feat(api): expose swagger security schemes
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
8e1a424fc8
commit
fb7702df67
4 changed files with 71 additions and 0 deletions
1
api.go
1
api.go
|
|
@ -52,6 +52,7 @@ type Engine struct {
|
|||
swaggerContactEmail string
|
||||
swaggerLicenseName string
|
||||
swaggerLicenseURL string
|
||||
swaggerSecuritySchemes map[string]any
|
||||
swaggerExternalDocsDescription string
|
||||
swaggerExternalDocsURL string
|
||||
pprofEnabled bool
|
||||
|
|
|
|||
32
options.go
32
options.go
|
|
@ -7,6 +7,7 @@ import (
|
|||
"log/slog"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
|
|
@ -239,6 +240,37 @@ func WithSwaggerLicense(name, url string) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithSwaggerSecuritySchemes merges custom OpenAPI security schemes into the
|
||||
// generated Swagger spec. Existing schemes are preserved unless the new map
|
||||
// defines the same key, in which case the later definition wins.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.WithSwaggerSecuritySchemes(map[string]any{
|
||||
// "apiKeyAuth": map[string]any{
|
||||
// "type": "apiKey",
|
||||
// "in": "header",
|
||||
// "name": "X-API-Key",
|
||||
// },
|
||||
// })
|
||||
func WithSwaggerSecuritySchemes(schemes map[string]any) Option {
|
||||
return func(e *Engine) {
|
||||
if len(schemes) == 0 {
|
||||
return
|
||||
}
|
||||
if e.swaggerSecuritySchemes == nil {
|
||||
e.swaggerSecuritySchemes = make(map[string]any, len(schemes))
|
||||
}
|
||||
for name, scheme := range schemes {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || scheme == nil {
|
||||
continue
|
||||
}
|
||||
e.swaggerSecuritySchemes[name] = scheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithSwaggerExternalDocs adds top-level external documentation metadata to
|
||||
// the generated Swagger spec.
|
||||
// Empty URLs are ignored; the description is optional.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
|
|||
Servers: slices.Clone(e.swaggerServers),
|
||||
LicenseName: e.swaggerLicenseName,
|
||||
LicenseURL: e.swaggerLicenseURL,
|
||||
SecuritySchemes: cloneSecuritySchemes(e.swaggerSecuritySchemes),
|
||||
ExternalDocsDescription: e.swaggerExternalDocsDescription,
|
||||
ExternalDocsURL: e.swaggerExternalDocsURL,
|
||||
}
|
||||
|
|
@ -43,3 +44,15 @@ func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
|
|||
|
||||
return builder
|
||||
}
|
||||
|
||||
func cloneSecuritySchemes(schemes map[string]any) map[string]any {
|
||||
if len(schemes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make(map[string]any, len(schemes))
|
||||
for name, scheme := range schemes {
|
||||
out[name] = scheme
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ func TestEngine_Good_OpenAPISpecBuilderCarriesEngineMetadata(t *testing.T) {
|
|||
api.WithSwaggerContact("API Support", "https://example.com/support", "support@example.com"),
|
||||
api.WithSwaggerServers("https://api.example.com", "/", "https://api.example.com"),
|
||||
api.WithSwaggerLicense("EUPL-1.2", "https://eupl.eu/1.2/en/"),
|
||||
api.WithSwaggerSecuritySchemes(map[string]any{
|
||||
"apiKeyAuth": map[string]any{
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-API-Key",
|
||||
},
|
||||
}),
|
||||
api.WithSwaggerExternalDocs("Developer guide", "https://example.com/docs"),
|
||||
api.WithWSPath("/socket"),
|
||||
api.WithWSHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})),
|
||||
|
|
@ -103,6 +110,24 @@ func TestEngine_Good_OpenAPISpecBuilderCarriesEngineMetadata(t *testing.T) {
|
|||
t.Fatalf("expected termsOfService to be preserved, got %v", info["termsOfService"])
|
||||
}
|
||||
|
||||
securitySchemes, ok := spec["components"].(map[string]any)["securitySchemes"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("expected securitySchemes metadata in generated spec")
|
||||
}
|
||||
apiKeyAuth, ok := securitySchemes["apiKeyAuth"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("expected apiKeyAuth security scheme in generated spec")
|
||||
}
|
||||
if apiKeyAuth["type"] != "apiKey" {
|
||||
t.Fatalf("expected apiKeyAuth.type=apiKey, got %v", apiKeyAuth["type"])
|
||||
}
|
||||
if apiKeyAuth["in"] != "header" {
|
||||
t.Fatalf("expected apiKeyAuth.in=header, got %v", apiKeyAuth["in"])
|
||||
}
|
||||
if apiKeyAuth["name"] != "X-API-Key" {
|
||||
t.Fatalf("expected apiKeyAuth.name=X-API-Key, got %v", apiKeyAuth["name"])
|
||||
}
|
||||
|
||||
externalDocs, ok := spec["externalDocs"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("expected externalDocs metadata in generated spec")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue