refactor(api): add runtime config snapshot

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 12:52:06 +00:00
parent 3c2f5512a8
commit 655faa1c31
3 changed files with 124 additions and 36 deletions

View file

@ -114,6 +114,55 @@ func TestEngine_CacheConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
}
}
func TestEngine_RuntimeConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
broker := api.NewSSEBroker()
e, err := api.New(
api.WithSwagger("Runtime API", "Runtime snapshot", "1.2.3"),
api.WithSwaggerPath("/docs"),
api.WithCacheLimits(5*time.Minute, 10, 1024),
api.WithI18n(api.I18nConfig{
DefaultLocale: "en-GB",
Supported: []string{"en-GB", "fr"},
}),
api.WithWSPath("/socket"),
api.WithSSE(broker),
api.WithSSEPath("/events"),
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
cfg := e.RuntimeConfig()
if !cfg.Swagger.Enabled {
t.Fatal("expected swagger snapshot to be enabled")
}
if cfg.Swagger.Path != "/docs" {
t.Fatalf("expected swagger path /docs, got %q", cfg.Swagger.Path)
}
if cfg.Transport.SwaggerPath != "/docs" {
t.Fatalf("expected transport swagger path /docs, got %q", cfg.Transport.SwaggerPath)
}
if !cfg.Cache.Enabled || cfg.Cache.TTL != 5*time.Minute {
t.Fatalf("expected cache snapshot to be populated, got %+v", cfg.Cache)
}
if cfg.I18n.DefaultLocale != "en-GB" {
t.Fatalf("expected default locale en-GB, got %q", cfg.I18n.DefaultLocale)
}
if !slices.Equal(cfg.I18n.Supported, []string{"en-GB", "fr"}) {
t.Fatalf("expected supported locales [en-GB fr], got %v", cfg.I18n.Supported)
}
}
func TestEngine_RuntimeConfig_Good_EmptyOnNilEngine(t *testing.T) {
var e *api.Engine
cfg := e.RuntimeConfig()
if cfg.Swagger.Enabled || cfg.Transport.SwaggerEnabled || cfg.Cache.Enabled || cfg.I18n.DefaultLocale != "" {
t.Fatalf("expected zero-value runtime config, got %+v", cfg)
}
}
func TestEngine_Register_Good_IgnoresNilGroups(t *testing.T) {
e, _ := api.New()

42
runtime_config.go Normal file
View file

@ -0,0 +1,42 @@
// SPDX-License-Identifier: EUPL-1.2
package api
// RuntimeConfig captures the engine's current runtime-facing configuration in
// a single snapshot.
//
// It groups the existing Swagger, transport, cache, and i18n snapshots so
// callers can inspect the active engine surface without joining multiple
// method results themselves.
//
// Example:
//
// cfg := engine.RuntimeConfig()
type RuntimeConfig struct {
Swagger SwaggerConfig
Transport TransportConfig
Cache CacheConfig
I18n I18nConfig
}
// RuntimeConfig returns a stable snapshot of the engine's current runtime
// configuration.
//
// The result clones the underlying snapshots so callers can safely retain or
// modify the returned value.
//
// Example:
//
// cfg := engine.RuntimeConfig()
func (e *Engine) RuntimeConfig() RuntimeConfig {
if e == nil {
return RuntimeConfig{}
}
return RuntimeConfig{
Swagger: e.SwaggerConfig(),
Transport: e.TransportConfig(),
Cache: e.CacheConfig(),
I18n: e.I18nConfig(),
}
}

View file

@ -46,48 +46,45 @@ func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
return &SpecBuilder{}
}
swagger := e.SwaggerConfig()
transport := e.TransportConfig()
runtime := e.RuntimeConfig()
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,
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 = 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
builder.SwaggerPath = runtime.Transport.SwaggerPath
builder.GraphQLEnabled = runtime.Transport.GraphQLEnabled
builder.GraphQLPath = runtime.Transport.GraphQLPath
builder.GraphQLPlayground = runtime.Transport.GraphQLPlayground
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
cache := e.CacheConfig()
builder.CacheEnabled = cache.Enabled
if cache.TTL > 0 {
builder.CacheTTL = cache.TTL.String()
builder.CacheEnabled = runtime.Cache.Enabled
if runtime.Cache.TTL > 0 {
builder.CacheTTL = runtime.Cache.TTL.String()
}
builder.CacheMaxEntries = cache.MaxEntries
builder.CacheMaxBytes = cache.MaxBytes
builder.CacheMaxEntries = runtime.Cache.MaxEntries
builder.CacheMaxBytes = runtime.Cache.MaxBytes
i18n := e.I18nConfig()
builder.I18nDefaultLocale = i18n.DefaultLocale
builder.I18nSupportedLocales = slices.Clone(i18n.Supported)
builder.I18nDefaultLocale = runtime.I18n.DefaultLocale
builder.I18nSupportedLocales = slices.Clone(runtime.I18n.Supported)
return builder
}