From 87a973a83e6c299631b002f51ade3009efda9ceb Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 07:31:45 +0000 Subject: [PATCH] feat(cmd/api): add SDK security scheme parity Co-Authored-By: Virgil --- cmd/api/cmd_sdk.go | 21 ++++++++++++++++++--- cmd/api/cmd_test.go | 17 ++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/cmd/api/cmd_sdk.go b/cmd/api/cmd_sdk.go index d98ae26..3559b6c 100644 --- a/cmd/api/cmd_sdk.go +++ b/cmd/api/cmd_sdk.go @@ -47,6 +47,7 @@ func addSDKCommand(parent *cli.Command) { externalDocsDescription string externalDocsURL string servers string + securitySchemes string ) cmd := cli.NewCommand("sdk", "Generate client SDKs from OpenAPI spec", "", func(cmd *cli.Command, args []string) error { @@ -57,7 +58,10 @@ func addSDKCommand(parent *cli.Command) { // If no spec file provided, generate one to a temp file. if specFile == "" { - builder := sdkSpecBuilder(title, description, version, swaggerPath, graphqlPath, graphqlPlayground, ssePath, wsPath, pprofEnabled, expvarEnabled, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers) + builder, err := sdkSpecBuilder(title, description, version, swaggerPath, graphqlPath, graphqlPlayground, ssePath, wsPath, pprofEnabled, expvarEnabled, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers, securitySchemes) + if err != nil { + return err + } groups := sdkSpecGroupsIter() tmpFile, err := os.CreateTemp("", "openapi-*.json") @@ -122,12 +126,13 @@ func addSDKCommand(parent *cli.Command) { cli.StringFlag(cmd, &externalDocsDescription, "external-docs-description", "", "", "OpenAPI external documentation description in generated spec") cli.StringFlag(cmd, &externalDocsURL, "external-docs-url", "", "", "OpenAPI external documentation URL in generated spec") cli.StringFlag(cmd, &servers, "server", "S", "", "Comma-separated OpenAPI server URL(s)") + cli.StringFlag(cmd, &securitySchemes, "security-schemes", "", "", "JSON object of custom OpenAPI security schemes") parent.AddCommand(cmd) } -func sdkSpecBuilder(title, description, version, swaggerPath, graphqlPath string, graphqlPlayground bool, ssePath, wsPath string, pprofEnabled, expvarEnabled bool, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers string) *goapi.SpecBuilder { - return &goapi.SpecBuilder{ +func sdkSpecBuilder(title, description, version, swaggerPath, graphqlPath string, graphqlPlayground bool, ssePath, wsPath string, pprofEnabled, expvarEnabled bool, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers, securitySchemes string) (*goapi.SpecBuilder, error) { + builder := &goapi.SpecBuilder{ Title: title, Description: description, Version: version, @@ -148,6 +153,16 @@ func sdkSpecBuilder(title, description, version, swaggerPath, graphqlPath string ExternalDocsDescription: externalDocsDescription, ExternalDocsURL: externalDocsURL, } + + if securitySchemes != "" { + schemes, err := parseSecuritySchemes(securitySchemes) + if err != nil { + return nil, err + } + builder.SecuritySchemes = schemes + } + + return builder, nil } func sdkSpecGroupsIter() iter.Seq[goapi.RouteGroup] { diff --git a/cmd/api/cmd_test.go b/cmd/api/cmd_test.go index 4d351f5..185d394 100644 --- a/cmd/api/cmd_test.go +++ b/cmd/api/cmd_test.go @@ -741,6 +741,9 @@ func TestAPISDKCmd_Good_ValidatesLanguage(t *testing.T) { if sdkCmd.Flag("server") == nil { t.Fatal("expected --server flag on sdk command") } + if sdkCmd.Flag("security-schemes") == nil { + t.Fatal("expected --security-schemes flag on sdk command") + } } func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) { @@ -753,7 +756,7 @@ func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) { api.RegisterSpecGroups(specCmdStubGroup{}) - builder := sdkSpecBuilder( + builder, err := sdkSpecBuilder( "Custom SDK API", "Custom SDK description", "9.9.9", @@ -773,7 +776,11 @@ func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) { "", "", "https://api.example.com, /, https://api.example.com", + `{"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}}`, ) + if err != nil { + t.Fatalf("unexpected error building sdk spec: %v", err) + } groups := collectRouteGroups(sdkSpecGroupsIter()) outputFile := t.TempDir() + "/spec.json" @@ -873,6 +880,14 @@ func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) { if servers[1].(map[string]any)["url"] != "/" { t.Fatalf("expected second server to be /, got %v", servers[1]) } + + securitySchemes, ok := spec["components"].(map[string]any)["securitySchemes"].(map[string]any) + if !ok { + t.Fatal("expected securitySchemes in generated spec") + } + if _, ok := securitySchemes["apiKeyAuth"].(map[string]any); !ok { + t.Fatalf("expected apiKeyAuth security scheme in generated spec, got %v", securitySchemes) + } } func TestAPISDKCmd_Good_SpecGroupsDeduplicateToolBridge(t *testing.T) {