From 655faa1c310d8245c7685d2ad3f4640eb6042147 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 12:52:06 +0000 Subject: [PATCH] refactor(api): add runtime config snapshot Co-Authored-By: Virgil --- modernization_test.go | 49 ++++++++++++++++++++++++++++++ runtime_config.go | 42 +++++++++++++++++++++++++ spec_builder_helper.go | 69 ++++++++++++++++++++---------------------- 3 files changed, 124 insertions(+), 36 deletions(-) create mode 100644 runtime_config.go diff --git a/modernization_test.go b/modernization_test.go index 7255667..8762bbc 100644 --- a/modernization_test.go +++ b/modernization_test.go @@ -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() diff --git a/runtime_config.go b/runtime_config.go new file mode 100644 index 0000000..ba468cc --- /dev/null +++ b/runtime_config.go @@ -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(), + } +} diff --git a/spec_builder_helper.go b/spec_builder_helper.go index 7242a69..1fb91fe 100644 --- a/spec_builder_helper.go +++ b/spec_builder_helper.go @@ -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 }