// SPDX-License-Identifier: EUPL-1.2 package api import ( "reflect" "slices" "strings" ) // 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 Path string 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, transport, cache, and i18n metadata. // // Example: // // builder := engine.OpenAPISpecBuilder() func (e *Engine) OpenAPISpecBuilder() *SpecBuilder { if e == nil { return &SpecBuilder{} } swagger := e.SwaggerConfig() transport := e.TransportConfig() builder := &SpecBuilder{ Title: swagger.Title, Summary: swagger.Summary, Description: swagger.Description, Version: swagger.Version, SwaggerEnabled: swagger.Enabled, 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.GraphQLEnabled = transport.GraphQLEnabled builder.GraphQLPath = transport.GraphQLPath builder.GraphQLPlayground = transport.GraphQLPlayground builder.WSPath = transport.WSPath builder.WSEnabled = transport.WSEnabled builder.SSEPath = transport.SSEPath builder.SSEEnabled = transport.SSEEnabled builder.PprofEnabled = transport.PprofEnabled builder.ExpvarEnabled = transport.ExpvarEnabled cache := e.CacheConfig() builder.CacheEnabled = cache.Enabled if cache.TTL > 0 { builder.CacheTTL = cache.TTL.String() } builder.CacheMaxEntries = cache.MaxEntries builder.CacheMaxBytes = cache.MaxBytes i18n := e.I18nConfig() builder.I18nDefaultLocale = i18n.DefaultLocale builder.I18nSupportedLocales = slices.Clone(i18n.Supported) 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{} } cfg := SwaggerConfig{ Enabled: e.swaggerEnabled, Title: e.swaggerTitle, Summary: e.swaggerSummary, Description: e.swaggerDesc, Version: e.swaggerVersion, TermsOfService: e.swaggerTermsOfService, ContactName: e.swaggerContactName, ContactURL: e.swaggerContactURL, ContactEmail: e.swaggerContactEmail, Servers: slices.Clone(e.swaggerServers), LicenseName: e.swaggerLicenseName, LicenseURL: e.swaggerLicenseURL, SecuritySchemes: cloneSecuritySchemes(e.swaggerSecuritySchemes), ExternalDocsDescription: e.swaggerExternalDocsDescription, ExternalDocsURL: e.swaggerExternalDocsURL, } if strings.TrimSpace(e.swaggerPath) != "" { cfg.Path = normaliseSwaggerPath(e.swaggerPath) } return cfg } 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] = cloneOpenAPIValue(scheme) } return out } // cloneOpenAPIValue recursively copies JSON-like OpenAPI values so callers can // safely retain and reuse their original maps after configuring an engine. func cloneOpenAPIValue(v any) any { switch value := v.(type) { case map[string]any: out := make(map[string]any, len(value)) for k, nested := range value { out[k] = cloneOpenAPIValue(nested) } return out case []any: out := make([]any, len(value)) for i, nested := range value { out[i] = cloneOpenAPIValue(nested) } return out default: rv := reflect.ValueOf(v) if !rv.IsValid() { return nil } switch rv.Kind() { case reflect.Map: out := reflect.MakeMapWithSize(rv.Type(), rv.Len()) for _, key := range rv.MapKeys() { cloned := cloneOpenAPIValue(rv.MapIndex(key).Interface()) if cloned == nil { out.SetMapIndex(key, reflect.Zero(rv.Type().Elem())) continue } out.SetMapIndex(key, reflect.ValueOf(cloned)) } return out.Interface() case reflect.Slice: if rv.IsNil() { return v } out := reflect.MakeSlice(rv.Type(), rv.Len(), rv.Len()) for i := 0; i < rv.Len(); i++ { cloned := cloneOpenAPIValue(rv.Index(i).Interface()) if cloned == nil { out.Index(i).Set(reflect.Zero(rv.Type().Elem())) continue } out.Index(i).Set(reflect.ValueOf(cloned)) } return out.Interface() default: return value } } }