Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath, errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim, core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(), core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives. Framework boundary exceptions preserved where stdlib types are required by external interfaces (Gin, net/http, CGo, Wails, bubbletea). Co-Authored-By: Virgil <virgil@lethean.io>
284 lines
8 KiB
Go
284 lines
8 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package api
|
|
|
|
import (
|
|
"reflect"
|
|
"slices"
|
|
|
|
core "dappco.re/go/core"
|
|
)
|
|
|
|
// 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, i18n, and Authentik metadata.
|
|
//
|
|
// Example:
|
|
//
|
|
// builder := engine.OpenAPISpecBuilder()
|
|
func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
|
|
if e == nil {
|
|
return &SpecBuilder{}
|
|
}
|
|
|
|
runtime := e.RuntimeConfig()
|
|
builder := &SpecBuilder{
|
|
Title: runtime.Swagger.Title,
|
|
Summary: runtime.Swagger.Summary,
|
|
Description: runtime.Swagger.Description,
|
|
Version: runtime.Swagger.Version,
|
|
SwaggerEnabled: runtime.Swagger.Enabled,
|
|
TermsOfService: runtime.Swagger.TermsOfService,
|
|
ContactName: runtime.Swagger.ContactName,
|
|
ContactURL: runtime.Swagger.ContactURL,
|
|
ContactEmail: runtime.Swagger.ContactEmail,
|
|
Servers: slices.Clone(runtime.Swagger.Servers),
|
|
LicenseName: runtime.Swagger.LicenseName,
|
|
LicenseURL: runtime.Swagger.LicenseURL,
|
|
SecuritySchemes: cloneSecuritySchemes(runtime.Swagger.SecuritySchemes),
|
|
ExternalDocsDescription: runtime.Swagger.ExternalDocsDescription,
|
|
ExternalDocsURL: runtime.Swagger.ExternalDocsURL,
|
|
}
|
|
|
|
builder.SwaggerPath = runtime.Transport.SwaggerPath
|
|
builder.GraphQLEnabled = runtime.GraphQL.Enabled
|
|
builder.GraphQLPath = runtime.GraphQL.Path
|
|
builder.GraphQLPlayground = runtime.GraphQL.Playground
|
|
builder.GraphQLPlaygroundPath = runtime.GraphQL.PlaygroundPath
|
|
builder.WSPath = runtime.Transport.WSPath
|
|
builder.WSEnabled = runtime.Transport.WSEnabled
|
|
builder.SSEPath = runtime.Transport.SSEPath
|
|
builder.SSEEnabled = runtime.Transport.SSEEnabled
|
|
builder.PprofEnabled = runtime.Transport.PprofEnabled
|
|
builder.ExpvarEnabled = runtime.Transport.ExpvarEnabled
|
|
|
|
builder.CacheEnabled = runtime.Cache.Enabled
|
|
if runtime.Cache.TTL > 0 {
|
|
builder.CacheTTL = runtime.Cache.TTL.String()
|
|
}
|
|
builder.CacheMaxEntries = runtime.Cache.MaxEntries
|
|
builder.CacheMaxBytes = runtime.Cache.MaxBytes
|
|
|
|
builder.I18nDefaultLocale = runtime.I18n.DefaultLocale
|
|
builder.I18nSupportedLocales = slices.Clone(runtime.I18n.Supported)
|
|
builder.AuthentikIssuer = runtime.Authentik.Issuer
|
|
builder.AuthentikClientID = runtime.Authentik.ClientID
|
|
builder.AuthentikTrustedProxy = runtime.Authentik.TrustedProxy
|
|
builder.AuthentikPublicPaths = slices.Clone(runtime.Authentik.PublicPaths)
|
|
|
|
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 core.Trim(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
|
|
}
|
|
|
|
func cloneRouteDescription(rd RouteDescription) RouteDescription {
|
|
out := rd
|
|
|
|
out.Tags = slices.Clone(rd.Tags)
|
|
out.Security = cloneSecurityRequirements(rd.Security)
|
|
out.Parameters = cloneParameterDescriptions(rd.Parameters)
|
|
out.RequestBody = cloneOpenAPIObject(rd.RequestBody)
|
|
out.RequestExample = cloneOpenAPIValue(rd.RequestExample)
|
|
out.Response = cloneOpenAPIObject(rd.Response)
|
|
out.ResponseExample = cloneOpenAPIValue(rd.ResponseExample)
|
|
out.ResponseHeaders = cloneStringMap(rd.ResponseHeaders)
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneParameterDescriptions(params []ParameterDescription) []ParameterDescription {
|
|
if params == nil {
|
|
return nil
|
|
}
|
|
if len(params) == 0 {
|
|
return []ParameterDescription{}
|
|
}
|
|
|
|
out := make([]ParameterDescription, len(params))
|
|
for i, param := range params {
|
|
out[i] = param
|
|
out[i].Schema = cloneOpenAPIObject(param.Schema)
|
|
out[i].Example = cloneOpenAPIValue(param.Example)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneSecurityRequirements(security []map[string][]string) []map[string][]string {
|
|
if security == nil {
|
|
return nil
|
|
}
|
|
if len(security) == 0 {
|
|
return []map[string][]string{}
|
|
}
|
|
|
|
out := make([]map[string][]string, len(security))
|
|
for i, requirement := range security {
|
|
if len(requirement) == 0 {
|
|
continue
|
|
}
|
|
|
|
cloned := make(map[string][]string, len(requirement))
|
|
for name, scopes := range requirement {
|
|
cloned[name] = slices.Clone(scopes)
|
|
}
|
|
out[i] = cloned
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneOpenAPIObject(v map[string]any) map[string]any {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
if len(v) == 0 {
|
|
return map[string]any{}
|
|
}
|
|
|
|
cloned, _ := cloneOpenAPIValue(v).(map[string]any)
|
|
return cloned
|
|
}
|
|
|
|
func cloneStringMap(v map[string]string) map[string]string {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
if len(v) == 0 {
|
|
return map[string]string{}
|
|
}
|
|
|
|
out := make(map[string]string, len(v))
|
|
for key, value := range v {
|
|
out[key] = value
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|