← back to Cli Printing Press
fix(cli): coerce number-typed --limit param to int (#1370)
80f5bc6ca5d1c5aae2ca0880d19a5cd2396046ab · 2026-05-13 21:38:39 -0700 · Trevin Chow
LLM-derived specs in --docs mode emit `limit: number` for endpoints
whose `--limit` flag is semantically a count. The generator then
declared `var flagLimit float64` and bound it via `Float64Var`, which
breaks the `truncateJSONArray(data, flagLimit)` call site because the
helper expects `int`. OpenAPI specs declare `integer` and never hit this
path, so no golden output changes.
Mirrors the existing per-name override convention in `goTypeForParam`,
`cobraFlagFuncForParam`, and `defaultValForParam`: a new
`isFlagLimitParam` predicate coerces `number`-typed `limit` flags to
`int`/`IntVar` regardless of the spec's numeric type declaration. Keyed
on the same case-insensitive match `endpointNeedsClientLimit` uses, so
the override fires exactly where the truncate caller lives.
Closes #1082
Files touched
M internal/generator/generator.goM internal/generator/limit_truncation_test.go
Diff
commit 80f5bc6ca5d1c5aae2ca0880d19a5cd2396046ab
Author: Trevin Chow <trevin@trevinchow.com>
Date: Wed May 13 21:38:39 2026 -0700
fix(cli): coerce number-typed --limit param to int (#1370)
LLM-derived specs in --docs mode emit `limit: number` for endpoints
whose `--limit` flag is semantically a count. The generator then
declared `var flagLimit float64` and bound it via `Float64Var`, which
breaks the `truncateJSONArray(data, flagLimit)` call site because the
helper expects `int`. OpenAPI specs declare `integer` and never hit this
path, so no golden output changes.
Mirrors the existing per-name override convention in `goTypeForParam`,
`cobraFlagFuncForParam`, and `defaultValForParam`: a new
`isFlagLimitParam` predicate coerces `number`-typed `limit` flags to
`int`/`IntVar` regardless of the spec's numeric type declaration. Keyed
on the same case-insensitive match `endpointNeedsClientLimit` uses, so
the override fires exactly where the truncate caller lives.
Closes #1082
---
internal/generator/generator.go | 39 +++++++++++++++++++++----
internal/generator/limit_truncation_test.go | 45 +++++++++++++++++++++++++++++
2 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index d91dd090..c21e4575 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -2890,6 +2890,19 @@ func isCursorParam(name string) bool {
return false
}
+// isFlagLimitParam returns true when a parameter is the canonical
+// pagination/truncation `--limit` flag, which is always count-shaped
+// (integer) regardless of whether the spec declares it as `number`.
+// LLM-derived specs in `--docs` mode have produced `limit: number`,
+// which previously emitted `Float64Var`/`float64 flagLimit` and broke
+// `truncateJSONArray(data, flagLimit)` at compile time because the
+// helper expects an `int`. Keyed on the same case-insensitive match
+// `endpointNeedsClientLimit` uses, so the override fires exactly where
+// the truncate caller lives.
+func isFlagLimitParam(name string) bool {
+ return strings.EqualFold(strings.TrimSpace(name), "limit")
+}
+
func primitiveKind(t string) string {
switch strings.ToLower(strings.TrimSpace(t)) {
case "string":
@@ -3033,9 +3046,11 @@ func cobraFlagFunc(t string) string {
}
// goTypeForParam returns the Go type for a parameter, overriding int→string
-// for ID-like parameters to avoid overflow and zero-value confusion, and
+// for ID-like parameters to avoid overflow and zero-value confusion,
// numeric→string for pagination cursors so they survive scientific-notation
-// rendering of large Unix timestamps and millisecond cursors.
+// rendering of large Unix timestamps and millisecond cursors, and
+// float→int for the canonical `--limit` flag whose semantics are always
+// a count.
func goTypeForParam(name, t string) string {
kind := primitiveKind(t)
if isIDParam(name) && kind == "int" {
@@ -3044,11 +3059,15 @@ func goTypeForParam(name, t string) string {
if isCursorParam(name) && (kind == "int" || kind == "float") {
return "string"
}
+ if isFlagLimitParam(name) && kind == "float" {
+ return "int"
+ }
return goType(t)
}
// cobraFlagFuncForParam returns the cobra flag function, overriding IntVar→StringVar
-// for ID-like parameters and Float64Var/IntVar→StringVar for pagination cursors.
+// for ID-like parameters, Float64Var/IntVar→StringVar for pagination cursors,
+// and Float64Var→IntVar for the canonical `--limit` flag.
func cobraFlagFuncForParam(name, t string) string {
kind := primitiveKind(t)
if isIDParam(name) && kind == "int" {
@@ -3057,12 +3076,17 @@ func cobraFlagFuncForParam(name, t string) string {
if isCursorParam(name) && (kind == "int" || kind == "float") {
return "StringVar"
}
+ if isFlagLimitParam(name) && kind == "float" {
+ return "IntVar"
+ }
return cobraFlagFunc(t)
}
// defaultValForParam returns the default value for a flag parameter,
-// overriding int→string for ID-like parameters and numeric→string for
-// pagination cursors so the StringVar default matches the StringVar field type.
+// overriding int→string for ID-like parameters, numeric→string for
+// pagination cursors so the StringVar default matches the StringVar field type,
+// and float→int for the canonical `--limit` flag so the IntVar default
+// matches its coerced int type.
func defaultValForParam(p spec.Param) string {
kind := primitiveKind(p.Type)
if isIDParam(p.Name) && kind == "int" {
@@ -3077,6 +3101,11 @@ func defaultValForParam(p spec.Param) string {
}
return `""`
}
+ if isFlagLimitParam(p.Name) && kind == "float" {
+ coerced := p
+ coerced.Type = "integer"
+ return defaultVal(coerced)
+ }
return defaultVal(p)
}
diff --git a/internal/generator/limit_truncation_test.go b/internal/generator/limit_truncation_test.go
index 1e994c80..84e5e5a2 100644
--- a/internal/generator/limit_truncation_test.go
+++ b/internal/generator/limit_truncation_test.go
@@ -136,3 +136,48 @@ func TestClientLimitGenerationEmitsHelperAndBuilds(t *testing.T) {
runGoCommand(t, outputDir, "build", "./internal/cli")
}
+
+// TestNumberTypedLimitParamCoercesToInt verifies that a spec declaring
+// `limit` as `number` (the LLM-derived `--docs`-mode shape from #1082)
+// still emits `var flagLimit int` and `IntVar`, so the generated
+// `truncateJSONArray(data, flagLimit)` call compiles. OpenAPI specs
+// declare `integer` and exercise the same path; this test pins the
+// override that prevents `Float64Var`/`float64 flagLimit` regression.
+func TestNumberTypedLimitParamCoercesToInt(t *testing.T) {
+ t.Parallel()
+
+ apiSpec := minimalSpec("limit-number")
+ apiSpec.Resources = map[string]spec.Resource{
+ "webhooks": {
+ Description: "Manage webhooks",
+ Endpoints: map[string]spec.Endpoint{
+ "list-payloads": {
+ Method: "GET",
+ Path: "/webhooks/payloads",
+ Description: "List webhook payloads",
+ Params: []spec.Param{{
+ Name: "limit",
+ Type: "number",
+ Default: 0.0,
+ }},
+ Response: spec.ResponseDef{Type: "array", Item: "Payload"},
+ },
+ },
+ },
+ }
+
+ outputDir := filepath.Join(t.TempDir(), "limit-number-pp-cli")
+ require.NoError(t, New(apiSpec, outputDir).Generate())
+
+ commandSrc := readGeneratedFile(t, outputDir, "internal", "cli", "promoted_webhooks.go")
+ assert.Contains(t, commandSrc, "var flagLimit int",
+ "number-typed limit param must coerce to int Go type")
+ assert.Contains(t, commandSrc, "IntVar(&flagLimit",
+ "number-typed limit param must bind via IntVar")
+ assert.NotContains(t, commandSrc, "Float64Var(&flagLimit",
+ "number-typed limit param must not emit Float64Var")
+ assert.NotContains(t, commandSrc, "var flagLimit float64",
+ "number-typed limit param must not declare flagLimit as float64")
+
+ runGoCommand(t, outputDir, "build", "./internal/cli")
+}
← a6010384 fix(cli): detect cross-cutting novel features in dogfood sco
·
back to Cli Printing Press
·
test(cli): pin args[] index for flag-exposed path param shap ca1de8fa →