feat(api): normalize CLI list arguments

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 16:31:22 +00:00
parent 1cc0f2fd48
commit 19838779ef
4 changed files with 58 additions and 22 deletions

31
cmd/api/cmd_args.go Normal file
View file

@ -0,0 +1,31 @@
// SPDX-License-Identifier: EUPL-1.2
package api
import "strings"
// splitUniqueCSV trims and deduplicates a comma-separated list while
// preserving the first occurrence of each value.
func splitUniqueCSV(raw string) []string {
if raw == "" {
return nil
}
parts := strings.Split(raw, ",")
values := make([]string, 0, len(parts))
seen := make(map[string]struct{}, len(parts))
for _, part := range parts {
value := strings.TrimSpace(part)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
values = append(values, value)
}
return values
}

View file

@ -25,8 +25,9 @@ func addSDKCommand(parent *cli.Command) {
)
cmd := cli.NewCommand("sdk", "Generate client SDKs from OpenAPI spec", "", func(cmd *cli.Command, args []string) error {
if lang == "" {
return coreerr.E("sdk.Generate", "--lang is required. Supported: "+strings.Join(goapi.SupportedLanguages(), ", "), nil)
languages := splitUniqueCSV(lang)
if len(languages) == 0 {
return coreerr.E("sdk.Generate", "--lang is required and must include at least one non-empty language. Supported: "+strings.Join(goapi.SupportedLanguages(), ", "), nil)
}
// If no spec file provided, generate one to a temp file.
@ -68,11 +69,7 @@ func addSDKCommand(parent *cli.Command) {
}
// Generate for each language.
for l := range strings.SplitSeq(lang, ",") {
l = strings.TrimSpace(l)
if l == "" {
continue
}
for _, l := range languages {
fmt.Fprintf(os.Stderr, "Generating %s SDK...\n", l)
if err := gen.Generate(context.Background(), l); err != nil {
return coreerr.E("sdk.Generate", "generate "+l, err)

View file

@ -5,7 +5,6 @@ package api
import (
"fmt"
"os"
"strings"
"forge.lthn.ai/core/cli/pkg/cli"
@ -60,17 +59,5 @@ func addSpecCommand(parent *cli.Command) {
}
func parseServers(raw string) []string {
if raw == "" {
return nil
}
parts := strings.Split(raw, ",")
servers := make([]string, 0, len(parts))
for _, part := range parts {
if server := strings.TrimSpace(part); server != "" {
servers = append(servers, server)
}
}
return servers
return splitUniqueCSV(raw)
}

View file

@ -99,7 +99,7 @@ func TestAPISpecCmd_Good_ServerFlagAddsServers(t *testing.T) {
AddAPICommands(root)
outputFile := t.TempDir() + "/spec.json"
root.SetArgs([]string{"api", "spec", "--server", "https://api.example.com,/", "--output", outputFile})
root.SetArgs([]string{"api", "spec", "--server", "https://api.example.com, /, https://api.example.com, ", "--output", outputFile})
root.SetErr(new(bytes.Buffer))
if err := root.Execute(); err != nil {
@ -123,6 +123,27 @@ func TestAPISpecCmd_Good_ServerFlagAddsServers(t *testing.T) {
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])
}
}
func TestAPISDKCmd_Bad_EmptyLanguages(t *testing.T) {
root := &cli.Command{Use: "root"}
AddAPICommands(root)
root.SetArgs([]string{"api", "sdk", "--lang", " , , "})
buf := new(bytes.Buffer)
root.SetOut(buf)
root.SetErr(buf)
err := root.Execute()
if err == nil {
t.Fatal("expected error when --lang only contains empty values")
}
}
func TestAPISDKCmd_Bad_NoLang(t *testing.T) {