feat(api): expose cache and i18n OpenAPI metadata
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
5de64a0a75
commit
f919e8a3be
4 changed files with 118 additions and 3 deletions
29
openapi.go
29
openapi.go
|
|
@ -10,11 +10,14 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"slices"
|
||||
)
|
||||
|
||||
// SpecBuilder constructs an OpenAPI 3.1 specification from registered RouteGroups.
|
||||
// Title, Summary, Description, Version, and optional contact/licence/terms metadata populate the
|
||||
// OpenAPI info block. Top-level external documentation metadata is also supported.
|
||||
// OpenAPI info block. Top-level external documentation metadata is also supported, along with
|
||||
// additive extension fields that describe runtime transport, cache, and i18n settings.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
|
|
@ -46,6 +49,12 @@ type SpecBuilder struct {
|
|||
ExternalDocsURL string
|
||||
PprofEnabled bool
|
||||
ExpvarEnabled bool
|
||||
CacheEnabled bool
|
||||
CacheTTL string
|
||||
CacheMaxEntries int
|
||||
CacheMaxBytes int
|
||||
I18nDefaultLocale string
|
||||
I18nSupportedLocales []string
|
||||
}
|
||||
|
||||
type preparedRouteGroup struct {
|
||||
|
|
@ -129,6 +138,24 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
if sb.ExpvarEnabled {
|
||||
spec["x-expvar-enabled"] = true
|
||||
}
|
||||
if sb.CacheEnabled {
|
||||
spec["x-cache-enabled"] = true
|
||||
}
|
||||
if ttl := strings.TrimSpace(sb.CacheTTL); ttl != "" {
|
||||
spec["x-cache-ttl"] = ttl
|
||||
}
|
||||
if sb.CacheMaxEntries > 0 {
|
||||
spec["x-cache-max-entries"] = sb.CacheMaxEntries
|
||||
}
|
||||
if sb.CacheMaxBytes > 0 {
|
||||
spec["x-cache-max-bytes"] = sb.CacheMaxBytes
|
||||
}
|
||||
if locale := strings.TrimSpace(sb.I18nDefaultLocale); locale != "" {
|
||||
spec["x-i18n-default-locale"] = locale
|
||||
}
|
||||
if len(sb.I18nSupportedLocales) > 0 {
|
||||
spec["x-i18n-supported-locales"] = slices.Clone(sb.I18nSupportedLocales)
|
||||
}
|
||||
|
||||
if sb.TermsOfService != "" {
|
||||
spec["info"].(map[string]any)["termsOfService"] = sb.TermsOfService
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"iter"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
|
@ -364,6 +365,54 @@ func TestSpecBuilder_Good_SwaggerUIPathExtension(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_CacheAndI18nExtensions(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
Description: "Runtime config test",
|
||||
Version: "1.0.0",
|
||||
CacheEnabled: true,
|
||||
CacheTTL: (5 * time.Minute).String(),
|
||||
CacheMaxEntries: 42,
|
||||
CacheMaxBytes: 8192,
|
||||
I18nDefaultLocale: "en-GB",
|
||||
I18nSupportedLocales: []string{"en-GB", "fr"},
|
||||
}
|
||||
|
||||
data, err := sb.Build(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal(data, &spec); err != nil {
|
||||
t.Fatalf("invalid JSON: %v", err)
|
||||
}
|
||||
|
||||
if got := spec["x-cache-enabled"]; got != true {
|
||||
t.Fatalf("expected x-cache-enabled=true, got %v", got)
|
||||
}
|
||||
if got := spec["x-cache-ttl"]; got != "5m0s" {
|
||||
t.Fatalf("expected x-cache-ttl=5m0s, got %v", got)
|
||||
}
|
||||
if got := spec["x-cache-max-entries"]; got != float64(42) {
|
||||
t.Fatalf("expected x-cache-max-entries=42, got %v", got)
|
||||
}
|
||||
if got := spec["x-cache-max-bytes"]; got != float64(8192) {
|
||||
t.Fatalf("expected x-cache-max-bytes=8192, got %v", got)
|
||||
}
|
||||
|
||||
if got := spec["x-i18n-default-locale"]; got != "en-GB" {
|
||||
t.Fatalf("expected x-i18n-default-locale=en-GB, got %v", got)
|
||||
}
|
||||
locales, ok := spec["x-i18n-supported-locales"].([]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected x-i18n-supported-locales array, got %T", spec["x-i18n-supported-locales"])
|
||||
}
|
||||
if len(locales) != 2 || locales[0] != "en-GB" || locales[1] != "fr" {
|
||||
t.Fatalf("expected supported locales [en-GB fr], got %v", locales)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_GraphQLEndpoint(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type SwaggerConfig struct {
|
|||
}
|
||||
|
||||
// OpenAPISpecBuilder returns a SpecBuilder populated from the engine's current
|
||||
// Swagger and transport metadata.
|
||||
// Swagger, transport, cache, and i18n metadata.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
|
|
@ -76,6 +76,18 @@ func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
|
@ -32,6 +33,11 @@ func TestEngine_Good_OpenAPISpecBuilderCarriesEngineMetadata(t *testing.T) {
|
|||
},
|
||||
}),
|
||||
api.WithSwaggerExternalDocs("Developer guide", "https://example.com/docs"),
|
||||
api.WithCacheLimits(5*time.Minute, 42, 8192),
|
||||
api.WithI18n(api.I18nConfig{
|
||||
DefaultLocale: "en-GB",
|
||||
Supported: []string{"en-GB", "fr"},
|
||||
}),
|
||||
api.WithWSPath("/socket"),
|
||||
api.WithWSHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})),
|
||||
api.WithGraphQL(newTestSchema(), api.WithPlayground(), api.WithGraphQLPath("/gql")),
|
||||
|
|
@ -105,6 +111,28 @@ func TestEngine_Good_OpenAPISpecBuilderCarriesEngineMetadata(t *testing.T) {
|
|||
if got := spec["x-expvar-enabled"]; got != true {
|
||||
t.Fatalf("expected x-expvar-enabled=true, got %v", got)
|
||||
}
|
||||
if got := spec["x-cache-enabled"]; got != true {
|
||||
t.Fatalf("expected x-cache-enabled=true, got %v", got)
|
||||
}
|
||||
if got := spec["x-cache-ttl"]; got != "5m0s" {
|
||||
t.Fatalf("expected x-cache-ttl=5m0s, got %v", got)
|
||||
}
|
||||
if got := spec["x-cache-max-entries"]; got != float64(42) {
|
||||
t.Fatalf("expected x-cache-max-entries=42, got %v", got)
|
||||
}
|
||||
if got := spec["x-cache-max-bytes"]; got != float64(8192) {
|
||||
t.Fatalf("expected x-cache-max-bytes=8192, got %v", got)
|
||||
}
|
||||
if got := spec["x-i18n-default-locale"]; got != "en-GB" {
|
||||
t.Fatalf("expected x-i18n-default-locale=en-GB, got %v", got)
|
||||
}
|
||||
locales, ok := spec["x-i18n-supported-locales"].([]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected x-i18n-supported-locales array, got %T", spec["x-i18n-supported-locales"])
|
||||
}
|
||||
if len(locales) != 2 || locales[0] != "en-GB" || locales[1] != "fr" {
|
||||
t.Fatalf("expected supported locales [en-GB fr], got %v", locales)
|
||||
}
|
||||
|
||||
contact, ok := info["contact"].(map[string]any)
|
||||
if !ok {
|
||||
|
|
@ -244,7 +272,6 @@ func TestEngine_Good_SwaggerConfigCarriesEngineMetadata(t *testing.T) {
|
|||
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))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue