From 19838779ef3acc6a17b9d023f9c9a3123e3f36f2 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 16:31:22 +0000 Subject: [PATCH] feat(api): normalize CLI list arguments Co-Authored-By: Virgil --- cmd/api/cmd_args.go | 31 +++++++++++++++++++++++++++++++ cmd/api/cmd_sdk.go | 11 ++++------- cmd/api/cmd_spec.go | 15 +-------------- cmd/api/cmd_test.go | 23 ++++++++++++++++++++++- 4 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 cmd/api/cmd_args.go diff --git a/cmd/api/cmd_args.go b/cmd/api/cmd_args.go new file mode 100644 index 0000000..2783b2e --- /dev/null +++ b/cmd/api/cmd_args.go @@ -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 +} diff --git a/cmd/api/cmd_sdk.go b/cmd/api/cmd_sdk.go index be5c9ee..c8c0b0a 100644 --- a/cmd/api/cmd_sdk.go +++ b/cmd/api/cmd_sdk.go @@ -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) diff --git a/cmd/api/cmd_spec.go b/cmd/api/cmd_spec.go index 6da7eac..97b3033 100644 --- a/cmd/api/cmd_spec.go +++ b/cmd/api/cmd_spec.go @@ -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) } diff --git a/cmd/api/cmd_test.go b/cmd/api/cmd_test.go index c3202e7..778e59a 100644 --- a/cmd/api/cmd_test.go +++ b/cmd/api/cmd_test.go @@ -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) {