diff --git a/cmd/api/cmd_sdk.go b/cmd/api/cmd_sdk.go index 1bc2e07..4da9401 100644 --- a/cmd/api/cmd_sdk.go +++ b/cmd/api/cmd_sdk.go @@ -16,12 +16,22 @@ import ( goapi "dappco.re/go/core/api" ) +const ( + defaultSDKTitle = "Lethean Core API" + defaultSDKDescription = "Lethean Core API" + defaultSDKVersion = "1.0.0" +) + func addSDKCommand(parent *cli.Command) { var ( lang string output string specFile string packageName string + title string + description string + version string + servers string ) cmd := cli.NewCommand("sdk", "Generate client SDKs from OpenAPI spec", "", func(cmd *cli.Command, args []string) error { @@ -32,14 +42,8 @@ func addSDKCommand(parent *cli.Command) { // If no spec file provided, generate one to a temp file. if specFile == "" { - builder := &goapi.SpecBuilder{ - Title: "Lethean Core API", - Description: "Lethean Core API", - Version: "1.0.0", - } - - bridge := goapi.NewToolBridge("/tools") - groups := append(goapi.RegisteredSpecGroups(), bridge) + builder := sdkSpecBuilder(title, description, version, servers) + groups := sdkSpecGroups() tmpFile, err := os.CreateTemp("", "openapi-*.json") if err != nil { @@ -84,6 +88,24 @@ func addSDKCommand(parent *cli.Command) { cli.StringFlag(cmd, &output, "output", "o", "./sdk", "Output directory for generated SDKs") cli.StringFlag(cmd, &specFile, "spec", "s", "", "Path to existing OpenAPI spec (generates from MCP tools if not provided)") cli.StringFlag(cmd, &packageName, "package", "p", "lethean", "Package name for generated SDK") + cli.StringFlag(cmd, &title, "title", "t", defaultSDKTitle, "API title in generated spec") + cli.StringFlag(cmd, &description, "description", "d", defaultSDKDescription, "API description in generated spec") + cli.StringFlag(cmd, &version, "version", "V", defaultSDKVersion, "API version in generated spec") + cli.StringFlag(cmd, &servers, "server", "S", "", "Comma-separated OpenAPI server URL(s)") parent.AddCommand(cmd) } + +func sdkSpecBuilder(title, description, version, servers string) *goapi.SpecBuilder { + return &goapi.SpecBuilder{ + Title: title, + Description: description, + Version: version, + Servers: parseServers(servers), + } +} + +func sdkSpecGroups() []goapi.RouteGroup { + bridge := goapi.NewToolBridge("/tools") + return append(goapi.RegisteredSpecGroups(), bridge) +} diff --git a/cmd/api/cmd_test.go b/cmd/api/cmd_test.go index 2f60b4a..10b0384 100644 --- a/cmd/api/cmd_test.go +++ b/cmd/api/cmd_test.go @@ -245,4 +245,73 @@ func TestAPISDKCmd_Good_ValidatesLanguage(t *testing.T) { if sdkCmd.Flag("package") == nil { t.Fatal("expected --package flag on sdk command") } + if sdkCmd.Flag("title") == nil { + t.Fatal("expected --title flag on sdk command") + } + if sdkCmd.Flag("description") == nil { + t.Fatal("expected --description flag on sdk command") + } + if sdkCmd.Flag("version") == nil { + t.Fatal("expected --version flag on sdk command") + } + if sdkCmd.Flag("server") == nil { + t.Fatal("expected --server flag on sdk command") + } +} + +func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) { + snapshot := api.RegisteredSpecGroups() + api.ResetSpecGroups() + t.Cleanup(func() { + api.ResetSpecGroups() + api.RegisterSpecGroups(snapshot...) + }) + + api.RegisterSpecGroups(specCmdStubGroup{}) + + builder := sdkSpecBuilder("Custom SDK API", "Custom SDK description", "9.9.9", "https://api.example.com, /, https://api.example.com") + groups := sdkSpecGroups() + + outputFile := t.TempDir() + "/spec.json" + if err := api.ExportSpecToFile(outputFile, "json", builder, groups); err != nil { + t.Fatalf("unexpected error writing temp spec: %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) + } + + info, ok := spec["info"].(map[string]any) + if !ok { + t.Fatal("expected info object in generated spec") + } + if info["title"] != "Custom SDK API" { + t.Fatalf("expected custom title, got %v", info["title"]) + } + if info["description"] != "Custom SDK description" { + t.Fatalf("expected custom description, got %v", info["description"]) + } + if info["version"] != "9.9.9" { + t.Fatalf("expected custom version, got %v", info["version"]) + } + + servers, ok := spec["servers"].([]any) + if !ok { + t.Fatalf("expected servers array in generated spec, got %T", spec["servers"]) + } + if len(servers) != 2 { + t.Fatalf("expected 2 servers, got %d", len(servers)) + } + if servers[0].(map[string]any)["url"] != "https://api.example.com" { + t.Fatalf("expected first server to be https://api.example.com, got %v", servers[0]) + } + if servers[1].(map[string]any)["url"] != "/" { + t.Fatalf("expected second server to be /, got %v", servers[1]) + } } diff --git a/docs/architecture.md b/docs/architecture.md index 6334db4..b418adb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -576,7 +576,9 @@ Generates an OpenAPI 3.1 specification from registered route groups. | `--output` | `-o` | (stdout) | Write spec to file | | `--format` | `-f` | `json` | Output format: `json` or `yaml` | | `--title` | `-t` | `Lethean Core API` | API title | +| `--description` | `-d` | `Lethean Core API` | API description | | `--version` | `-V` | `1.0.0` | API version | +| `--server` | `-S` | (none) | Comma-separated OpenAPI server URL(s) | ### `core api sdk` @@ -588,6 +590,10 @@ Generates client SDKs from an OpenAPI spec using `openapi-generator-cli`. | `--output` | `-o` | `./sdk` | Output directory | | `--spec` | `-s` | (auto-generated) | Path to existing OpenAPI spec | | `--package` | `-p` | `lethean` | Package name for generated SDK | +| `--title` | `-t` | `Lethean Core API` | API title in generated spec | +| `--description` | `-d` | `Lethean Core API` | API description in generated spec | +| `--version` | `-V` | `1.0.0` | API version in generated spec | +| `--server` | `-S` | (none) | Comma-separated OpenAPI server URL(s) | ---