feat(api): expose SDK spec metadata flags

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 19:09:59 +00:00
parent 0144244ccd
commit 862604dc22
3 changed files with 105 additions and 8 deletions

View file

@ -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)
}

View file

@ -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])
}
}

View file

@ -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) |
---