feat(cmd/api): expose cache and i18n spec flags

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 12:43:06 +00:00
parent 5c067b3dae
commit 814c1b6233
4 changed files with 206 additions and 20 deletions

View file

@ -39,6 +39,12 @@ func addSDKCommand(parent *cli.Command) {
wsPath string
pprofEnabled bool
expvarEnabled bool
cacheEnabled bool
cacheTTL string
cacheMaxEntries int
cacheMaxBytes int
i18nDefaultLocale string
i18nSupportedLocales string
termsURL string
contactName string
contactURL string
@ -84,6 +90,12 @@ func addSDKCommand(parent *cli.Command) {
wsPath: wsPath,
pprofEnabled: pprofEnabled,
expvarEnabled: expvarEnabled,
cacheEnabled: cacheEnabled,
cacheTTL: cacheTTL,
cacheMaxEntries: cacheMaxEntries,
cacheMaxBytes: cacheMaxBytes,
i18nDefaultLocale: i18nDefaultLocale,
i18nSupportedLocales: i18nSupportedLocales,
termsURL: termsURL,
contactName: contactName,
contactURL: contactURL,
@ -146,6 +158,12 @@ func addSDKCommand(parent *cli.Command) {
cli.StringFlag(cmd, &wsPath, "ws-path", "", "", "WebSocket endpoint path in generated spec")
cli.BoolFlag(cmd, &pprofEnabled, "pprof", "", false, "Include pprof endpoints in generated spec")
cli.BoolFlag(cmd, &expvarEnabled, "expvar", "", false, "Include expvar endpoint in generated spec")
cli.BoolFlag(cmd, &cacheEnabled, "cache", "", false, "Include cache metadata in generated spec")
cli.StringFlag(cmd, &cacheTTL, "cache-ttl", "", "", "Cache TTL in generated spec")
cli.IntFlag(cmd, &cacheMaxEntries, "cache-max-entries", "", 0, "Cache max entries in generated spec")
cli.IntFlag(cmd, &cacheMaxBytes, "cache-max-bytes", "", 0, "Cache max bytes in generated spec")
cli.StringFlag(cmd, &i18nDefaultLocale, "i18n-default-locale", "", "", "Default locale in generated spec")
cli.StringFlag(cmd, &i18nSupportedLocales, "i18n-supported-locales", "", "", "Comma-separated supported locales in generated spec")
cli.StringFlag(cmd, &termsURL, "terms-of-service", "", "", "OpenAPI terms of service URL in generated spec")
cli.StringFlag(cmd, &contactName, "contact-name", "", "", "OpenAPI contact name in generated spec")
cli.StringFlag(cmd, &contactURL, "contact-url", "", "", "OpenAPI contact URL in generated spec")
@ -173,6 +191,12 @@ func sdkSpecBuilder(cfg specBuilderConfig) (*goapi.SpecBuilder, error) {
wsPath: cfg.wsPath,
pprofEnabled: cfg.pprofEnabled,
expvarEnabled: cfg.expvarEnabled,
cacheEnabled: cfg.cacheEnabled,
cacheTTL: cfg.cacheTTL,
cacheMaxEntries: cfg.cacheMaxEntries,
cacheMaxBytes: cfg.cacheMaxBytes,
i18nDefaultLocale: cfg.i18nDefaultLocale,
i18nSupportedLocales: cfg.i18nSupportedLocales,
termsURL: cfg.termsURL,
contactName: cfg.contactName,
contactURL: cfg.contactURL,

View file

@ -28,6 +28,12 @@ func addSpecCommand(parent *cli.Command) {
wsPath string
pprofEnabled bool
expvarEnabled bool
cacheEnabled bool
cacheTTL string
cacheMaxEntries int
cacheMaxBytes int
i18nDefaultLocale string
i18nSupportedLocales string
termsURL string
contactName string
contactURL string
@ -54,6 +60,12 @@ func addSpecCommand(parent *cli.Command) {
wsPath: wsPath,
pprofEnabled: pprofEnabled,
expvarEnabled: expvarEnabled,
cacheEnabled: cacheEnabled,
cacheTTL: cacheTTL,
cacheMaxEntries: cacheMaxEntries,
cacheMaxBytes: cacheMaxBytes,
i18nDefaultLocale: i18nDefaultLocale,
i18nSupportedLocales: i18nSupportedLocales,
termsURL: termsURL,
contactName: contactName,
contactURL: contactURL,
@ -96,6 +108,12 @@ func addSpecCommand(parent *cli.Command) {
cli.StringFlag(cmd, &wsPath, "ws-path", "", "", "WebSocket endpoint path in generated spec")
cli.BoolFlag(cmd, &pprofEnabled, "pprof", "", false, "Include pprof endpoints in generated spec")
cli.BoolFlag(cmd, &expvarEnabled, "expvar", "", false, "Include expvar endpoint in generated spec")
cli.BoolFlag(cmd, &cacheEnabled, "cache", "", false, "Include cache metadata in generated spec")
cli.StringFlag(cmd, &cacheTTL, "cache-ttl", "", "", "Cache TTL in generated spec")
cli.IntFlag(cmd, &cacheMaxEntries, "cache-max-entries", "", 0, "Cache max entries in generated spec")
cli.IntFlag(cmd, &cacheMaxBytes, "cache-max-bytes", "", 0, "Cache max bytes in generated spec")
cli.StringFlag(cmd, &i18nDefaultLocale, "i18n-default-locale", "", "", "Default locale in generated spec")
cli.StringFlag(cmd, &i18nSupportedLocales, "i18n-supported-locales", "", "", "Comma-separated supported locales in generated spec")
cli.StringFlag(cmd, &termsURL, "terms-of-service", "", "", "OpenAPI terms of service URL in spec")
cli.StringFlag(cmd, &contactName, "contact-name", "", "", "OpenAPI contact name in spec")
cli.StringFlag(cmd, &contactURL, "contact-url", "", "", "OpenAPI contact URL in spec")

View file

@ -115,6 +115,24 @@ func TestAPISpecCmd_Good_JSON(t *testing.T) {
if specCmd.Flag("expvar") == nil {
t.Fatal("expected --expvar flag on spec command")
}
if specCmd.Flag("cache") == nil {
t.Fatal("expected --cache flag on spec command")
}
if specCmd.Flag("cache-ttl") == nil {
t.Fatal("expected --cache-ttl flag on spec command")
}
if specCmd.Flag("cache-max-entries") == nil {
t.Fatal("expected --cache-max-entries flag on spec command")
}
if specCmd.Flag("cache-max-bytes") == nil {
t.Fatal("expected --cache-max-bytes flag on spec command")
}
if specCmd.Flag("i18n-default-locale") == nil {
t.Fatal("expected --i18n-default-locale flag on spec command")
}
if specCmd.Flag("i18n-supported-locales") == nil {
t.Fatal("expected --i18n-supported-locales flag on spec command")
}
if specCmd.Flag("terms-of-service") == nil {
t.Fatal("expected --terms-of-service flag on spec command")
}
@ -216,6 +234,61 @@ func TestAPISpecCmd_Good_SummaryPopulatesSpecInfo(t *testing.T) {
}
}
func TestAPISpecCmd_Good_CacheAndI18nFlagsPopulateSpec(t *testing.T) {
root := &cli.Command{Use: "root"}
AddAPICommands(root)
outputFile := t.TempDir() + "/spec.json"
root.SetArgs([]string{
"api", "spec",
"--cache",
"--cache-ttl", "5m0s",
"--cache-max-entries", "42",
"--cache-max-bytes", "8192",
"--i18n-default-locale", "en-GB",
"--i18n-supported-locales", "en-GB,fr,en-GB",
"--output", outputFile,
})
root.SetErr(new(bytes.Buffer))
if err := root.Execute(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
data, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("expected spec file to be written: %v", err)
}
var spec map[string]any
if err := json.Unmarshal(data, &spec); err != nil {
t.Fatalf("expected valid JSON spec, got error: %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 TestAPISpecCmd_Good_GraphQLPlaygroundFlagPopulatesSpecPaths(t *testing.T) {
root := &cli.Command{Use: "root"}
AddAPICommands(root)
@ -758,6 +831,24 @@ func TestAPISDKCmd_Good_ValidatesLanguage(t *testing.T) {
if sdkCmd.Flag("expvar") == nil {
t.Fatal("expected --expvar flag on sdk command")
}
if sdkCmd.Flag("cache") == nil {
t.Fatal("expected --cache flag on sdk command")
}
if sdkCmd.Flag("cache-ttl") == nil {
t.Fatal("expected --cache-ttl flag on sdk command")
}
if sdkCmd.Flag("cache-max-entries") == nil {
t.Fatal("expected --cache-max-entries flag on sdk command")
}
if sdkCmd.Flag("cache-max-bytes") == nil {
t.Fatal("expected --cache-max-bytes flag on sdk command")
}
if sdkCmd.Flag("i18n-default-locale") == nil {
t.Fatal("expected --i18n-default-locale flag on sdk command")
}
if sdkCmd.Flag("i18n-supported-locales") == nil {
t.Fatal("expected --i18n-supported-locales flag on sdk command")
}
if sdkCmd.Flag("terms-of-service") == nil {
t.Fatal("expected --terms-of-service flag on sdk command")
}
@ -795,25 +886,31 @@ func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) {
api.RegisterSpecGroups(specCmdStubGroup{})
builder, err := sdkSpecBuilder(specBuilderConfig{
title: "Custom SDK API",
summary: "Custom SDK overview",
description: "Custom SDK description",
version: "9.9.9",
swaggerPath: "/docs",
graphqlPath: "/gql",
graphqlPlayground: true,
ssePath: "/events",
wsPath: "/ws",
pprofEnabled: true,
expvarEnabled: true,
termsURL: "https://example.com/terms",
contactName: "SDK Support",
contactURL: "https://example.com/support",
contactEmail: "support@example.com",
licenseName: "EUPL-1.2",
licenseURL: "https://eupl.eu/1.2/en/",
servers: "https://api.example.com, /, https://api.example.com",
securitySchemes: `{"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}}`,
title: "Custom SDK API",
summary: "Custom SDK overview",
description: "Custom SDK description",
version: "9.9.9",
swaggerPath: "/docs",
graphqlPath: "/gql",
graphqlPlayground: true,
ssePath: "/events",
wsPath: "/ws",
pprofEnabled: true,
expvarEnabled: true,
cacheEnabled: true,
cacheTTL: "5m0s",
cacheMaxEntries: 42,
cacheMaxBytes: 8192,
i18nDefaultLocale: "en-GB",
i18nSupportedLocales: "en-GB,fr,en-GB",
termsURL: "https://example.com/terms",
contactName: "SDK Support",
contactURL: "https://example.com/support",
contactEmail: "support@example.com",
licenseName: "EUPL-1.2",
licenseURL: "https://eupl.eu/1.2/en/",
servers: "https://api.example.com, /, https://api.example.com",
securitySchemes: `{"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}}`,
})
if err != nil {
t.Fatalf("unexpected error building sdk spec: %v", err)
@ -878,6 +975,29 @@ func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) {
t.Fatal("expected expvar path to be included in generated spec")
}
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)
}
if info["termsOfService"] != "https://example.com/terms" {
t.Fatalf("expected termsOfService to be preserved, got %v", info["termsOfService"])
}

View file

@ -2,7 +2,11 @@
package api
import goapi "dappco.re/go/core/api"
import (
"strings"
goapi "dappco.re/go/core/api"
)
type specBuilderConfig struct {
title string
@ -16,6 +20,12 @@ type specBuilderConfig struct {
wsPath string
pprofEnabled bool
expvarEnabled bool
cacheEnabled bool
cacheTTL string
cacheMaxEntries int
cacheMaxBytes int
i18nDefaultLocale string
i18nSupportedLocales string
termsURL string
contactName string
contactURL string
@ -41,6 +51,11 @@ func newSpecBuilder(cfg specBuilderConfig) (*goapi.SpecBuilder, error) {
WSPath: cfg.wsPath,
PprofEnabled: cfg.pprofEnabled,
ExpvarEnabled: cfg.expvarEnabled,
CacheEnabled: cfg.cacheEnabled || strings.TrimSpace(cfg.cacheTTL) != "" || cfg.cacheMaxEntries > 0 || cfg.cacheMaxBytes > 0,
CacheTTL: strings.TrimSpace(cfg.cacheTTL),
CacheMaxEntries: cfg.cacheMaxEntries,
CacheMaxBytes: cfg.cacheMaxBytes,
I18nDefaultLocale: strings.TrimSpace(cfg.i18nDefaultLocale),
TermsOfService: cfg.termsURL,
ContactName: cfg.contactName,
ContactURL: cfg.contactURL,
@ -52,6 +67,11 @@ func newSpecBuilder(cfg specBuilderConfig) (*goapi.SpecBuilder, error) {
ExternalDocsURL: cfg.externalDocsURL,
}
builder.I18nSupportedLocales = parseLocales(cfg.i18nSupportedLocales)
if builder.I18nDefaultLocale == "" && len(builder.I18nSupportedLocales) > 0 {
builder.I18nDefaultLocale = "en"
}
if cfg.securitySchemes != "" {
schemes, err := parseSecuritySchemes(cfg.securitySchemes)
if err != nil {
@ -62,3 +82,7 @@ func newSpecBuilder(cfg specBuilderConfig) (*goapi.SpecBuilder, error) {
return builder, nil
}
func parseLocales(raw string) []string {
return splitUniqueCSV(raw)
}