feat(api): expose swagger config snapshot
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
d225fd3178
commit
d06f4957a3
2 changed files with 155 additions and 10 deletions
|
|
@ -7,6 +7,32 @@ import (
|
|||
"slices"
|
||||
)
|
||||
|
||||
// SwaggerConfig captures the configured Swagger/OpenAPI metadata for an Engine.
|
||||
//
|
||||
// It is intentionally small and serialisable so callers can inspect the active
|
||||
// documentation surface without rebuilding an OpenAPI document.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cfg := api.SwaggerConfig{Title: "Service", Summary: "Public API"}
|
||||
type SwaggerConfig struct {
|
||||
Enabled bool
|
||||
Title string
|
||||
Summary string
|
||||
Description string
|
||||
Version string
|
||||
TermsOfService string
|
||||
ContactName string
|
||||
ContactURL string
|
||||
ContactEmail string
|
||||
Servers []string
|
||||
LicenseName string
|
||||
LicenseURL string
|
||||
SecuritySchemes map[string]any
|
||||
ExternalDocsDescription string
|
||||
ExternalDocsURL string
|
||||
}
|
||||
|
||||
// OpenAPISpecBuilder returns a SpecBuilder populated from the engine's current
|
||||
// Swagger and transport metadata.
|
||||
//
|
||||
|
|
@ -18,8 +44,51 @@ func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
|
|||
return &SpecBuilder{}
|
||||
}
|
||||
|
||||
swagger := e.SwaggerConfig()
|
||||
transport := e.TransportConfig()
|
||||
builder := &SpecBuilder{
|
||||
Title: swagger.Title,
|
||||
Summary: swagger.Summary,
|
||||
Description: swagger.Description,
|
||||
Version: swagger.Version,
|
||||
TermsOfService: swagger.TermsOfService,
|
||||
ContactName: swagger.ContactName,
|
||||
ContactURL: swagger.ContactURL,
|
||||
ContactEmail: swagger.ContactEmail,
|
||||
Servers: slices.Clone(swagger.Servers),
|
||||
LicenseName: swagger.LicenseName,
|
||||
LicenseURL: swagger.LicenseURL,
|
||||
SecuritySchemes: cloneSecuritySchemes(swagger.SecuritySchemes),
|
||||
ExternalDocsDescription: swagger.ExternalDocsDescription,
|
||||
ExternalDocsURL: swagger.ExternalDocsURL,
|
||||
}
|
||||
|
||||
builder.SwaggerPath = transport.SwaggerPath
|
||||
builder.GraphQLPath = transport.GraphQLPath
|
||||
builder.GraphQLPlayground = transport.GraphQLPlayground
|
||||
builder.WSPath = transport.WSPath
|
||||
builder.SSEPath = transport.SSEPath
|
||||
builder.PprofEnabled = transport.PprofEnabled
|
||||
builder.ExpvarEnabled = transport.ExpvarEnabled
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
// SwaggerConfig returns the currently configured Swagger metadata for the engine.
|
||||
//
|
||||
// The result snapshots the Engine state at call time and clones slices/maps so
|
||||
// callers can safely reuse or modify the returned value.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cfg := engine.SwaggerConfig()
|
||||
func (e *Engine) SwaggerConfig() SwaggerConfig {
|
||||
if e == nil {
|
||||
return SwaggerConfig{}
|
||||
}
|
||||
|
||||
return SwaggerConfig{
|
||||
Enabled: e.swaggerEnabled,
|
||||
Title: e.swaggerTitle,
|
||||
Summary: e.swaggerSummary,
|
||||
Description: e.swaggerDesc,
|
||||
|
|
@ -35,16 +104,6 @@ func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
|
|||
ExternalDocsDescription: e.swaggerExternalDocsDescription,
|
||||
ExternalDocsURL: e.swaggerExternalDocsURL,
|
||||
}
|
||||
|
||||
builder.SwaggerPath = transport.SwaggerPath
|
||||
builder.GraphQLPath = transport.GraphQLPath
|
||||
builder.GraphQLPlayground = transport.GraphQLPlayground
|
||||
builder.WSPath = transport.WSPath
|
||||
builder.SSEPath = transport.SSEPath
|
||||
builder.PprofEnabled = transport.PprofEnabled
|
||||
builder.ExpvarEnabled = transport.ExpvarEnabled
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
func cloneSecuritySchemes(schemes map[string]any) map[string]any {
|
||||
|
|
|
|||
|
|
@ -178,6 +178,92 @@ func TestEngine_Good_OpenAPISpecBuilderCarriesEngineMetadata(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEngine_Good_SwaggerConfigCarriesEngineMetadata(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
e, err := api.New(
|
||||
api.WithSwagger("Engine API", "Engine metadata", "2.0.0"),
|
||||
api.WithSwaggerSummary("Engine overview"),
|
||||
api.WithSwaggerTermsOfService("https://example.com/terms"),
|
||||
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"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
cfg := e.SwaggerConfig()
|
||||
if !cfg.Enabled {
|
||||
t.Fatal("expected Swagger to be enabled")
|
||||
}
|
||||
if cfg.Title != "Engine API" {
|
||||
t.Fatalf("expected title Engine API, got %q", cfg.Title)
|
||||
}
|
||||
if cfg.Description != "Engine metadata" {
|
||||
t.Fatalf("expected description Engine metadata, got %q", cfg.Description)
|
||||
}
|
||||
if cfg.Version != "2.0.0" {
|
||||
t.Fatalf("expected version 2.0.0, got %q", cfg.Version)
|
||||
}
|
||||
if cfg.Summary != "Engine overview" {
|
||||
t.Fatalf("expected summary Engine overview, got %q", cfg.Summary)
|
||||
}
|
||||
if cfg.TermsOfService != "https://example.com/terms" {
|
||||
t.Fatalf("expected termsOfService to be preserved, got %q", cfg.TermsOfService)
|
||||
}
|
||||
if cfg.ContactName != "API Support" {
|
||||
t.Fatalf("expected contact name API Support, got %q", cfg.ContactName)
|
||||
}
|
||||
if cfg.LicenseName != "EUPL-1.2" {
|
||||
t.Fatalf("expected licence name EUPL-1.2, got %q", cfg.LicenseName)
|
||||
}
|
||||
if cfg.ExternalDocsURL != "https://example.com/docs" {
|
||||
t.Fatalf("expected external docs URL https://example.com/docs, got %q", cfg.ExternalDocsURL)
|
||||
}
|
||||
|
||||
if len(cfg.Servers) != 2 {
|
||||
t.Fatalf("expected 2 normalised servers, got %d", len(cfg.Servers))
|
||||
}
|
||||
if cfg.Servers[0] != "https://api.example.com" {
|
||||
t.Fatalf("expected first server to be https://api.example.com, got %q", cfg.Servers[0])
|
||||
}
|
||||
if cfg.Servers[1] != "/" {
|
||||
t.Fatalf("expected second server to be /, got %q", cfg.Servers[1])
|
||||
}
|
||||
|
||||
apiKeyAuth, ok := cfg.SecuritySchemes["apiKeyAuth"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("expected apiKeyAuth security scheme in Swagger config")
|
||||
}
|
||||
if apiKeyAuth["name"] != "X-API-Key" {
|
||||
t.Fatalf("expected apiKeyAuth.name=X-API-Key, got %v", apiKeyAuth["name"])
|
||||
}
|
||||
|
||||
cfg.Servers[0] = "https://mutated.example.com"
|
||||
apiKeyAuth["name"] = "Changed"
|
||||
|
||||
reshot := e.SwaggerConfig()
|
||||
if reshot.Servers[0] != "https://api.example.com" {
|
||||
t.Fatalf("expected engine servers to be cloned, got %q", reshot.Servers[0])
|
||||
}
|
||||
reshotScheme, ok := reshot.SecuritySchemes["apiKeyAuth"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("expected apiKeyAuth security scheme in cloned Swagger config")
|
||||
}
|
||||
if reshotScheme["name"] != "X-API-Key" {
|
||||
t.Fatalf("expected cloned security scheme name X-API-Key, got %v", reshotScheme["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngine_Good_TransportConfigCarriesEngineMetadata(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue