diff --git a/cmd/pipeline/image/list/cmd.go b/cmd/pipeline/image/list/cmd.go index c7bd8ec7d..91203635e 100644 --- a/cmd/pipeline/image/list/cmd.go +++ b/cmd/pipeline/image/list/cmd.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/datarobot/cli/internal/auth" + "github.com/datarobot/cli/internal/countflags" "github.com/datarobot/cli/internal/outputformat" "github.com/datarobot/cli/internal/pipeline" "github.com/datarobot/cli/internal/telemetry" @@ -60,8 +61,8 @@ Example: outputformat.AddFlag(cmd, &outputFormat) - cmd.Flags().IntVar(&offset, "offset", 0, "Pagination offset") - cmd.Flags().IntVar(&limit, "limit", 100, "Maximum number of images to return") + cmd.Flags().Var(countflags.NonNegativeInt(&offset, 0), "offset", "Pagination offset") + cmd.Flags().Var(countflags.PositiveInt(&limit, 100), "limit", "Maximum number of images to return") telemetry.TrackWith(cmd, func(_ *cobra.Command, _ []string) map[string]any { return map[string]any{ diff --git a/cmd/pipeline/image/list/cmd_test.go b/cmd/pipeline/image/list/cmd_test.go index 8bfe11528..d97f4b2cf 100644 --- a/cmd/pipeline/image/list/cmd_test.go +++ b/cmd/pipeline/image/list/cmd_test.go @@ -47,3 +47,16 @@ func TestCmd_HasExpectedFlags(t *testing.T) { assert.NotNilf(t, cmd.Flags().Lookup(name), "expected --%s flag", name) } } + +func TestCmd_RejectsInvalidLimit(t *testing.T) { + // Rejected at parse time by countflags.PositiveInt, before any request. + err := runCmd(t, "--limit", "0") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a positive integer") +} + +func TestCmd_RejectsInvalidOffset(t *testing.T) { + err := runCmd(t, "--offset", "-1") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a non-negative integer") +} diff --git a/cmd/pipeline/input/list/cmd.go b/cmd/pipeline/input/list/cmd.go index ab1608f96..d9056cb3b 100644 --- a/cmd/pipeline/input/list/cmd.go +++ b/cmd/pipeline/input/list/cmd.go @@ -20,6 +20,7 @@ import ( "github.com/datarobot/cli/cmd/internal/errmsg" "github.com/datarobot/cli/cmd/pipeline/scopeflag" "github.com/datarobot/cli/internal/auth" + "github.com/datarobot/cli/internal/countflags" "github.com/datarobot/cli/internal/outputformat" "github.com/datarobot/cli/internal/pipeline" "github.com/datarobot/cli/internal/telemetry" @@ -73,8 +74,8 @@ Example: flags.Bind(cmd) _ = cmd.MarkFlagRequired("pipeline") - cmd.Flags().IntVar(&offset, "offset", 0, "Pagination offset") - cmd.Flags().IntVar(&limit, "limit", 100, "Maximum number of inputs to return") + cmd.Flags().Var(countflags.NonNegativeInt(&offset, 0), "offset", "Pagination offset") + cmd.Flags().Var(countflags.PositiveInt(&limit, 100), "limit", "Maximum number of inputs to return") telemetry.TrackWith(cmd, func(_ *cobra.Command, _ []string) map[string]any { return map[string]any{ diff --git a/cmd/pipeline/input/list/cmd_test.go b/cmd/pipeline/input/list/cmd_test.go index c39ace176..ee9b3a67e 100644 --- a/cmd/pipeline/input/list/cmd_test.go +++ b/cmd/pipeline/input/list/cmd_test.go @@ -59,3 +59,16 @@ func TestCmd_HasExpectedFlags(t *testing.T) { assert.NotNilf(t, cmd.Flags().Lookup(name), "expected --%s flag", name) } } + +func TestCmd_RejectsInvalidLimit(t *testing.T) { + // Rejected at parse time by countflags.PositiveInt, before any request. + err := runCmd(t, "--pipeline", "p", "--limit", "0") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a positive integer") +} + +func TestCmd_RejectsInvalidOffset(t *testing.T) { + err := runCmd(t, "--pipeline", "p", "--offset", "-1") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a non-negative integer") +} diff --git a/cmd/pipeline/list/cmd.go b/cmd/pipeline/list/cmd.go index 176b50492..ec7b3f306 100644 --- a/cmd/pipeline/list/cmd.go +++ b/cmd/pipeline/list/cmd.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/datarobot/cli/internal/auth" + "github.com/datarobot/cli/internal/countflags" "github.com/datarobot/cli/internal/outputformat" "github.com/datarobot/cli/internal/pipeline" "github.com/datarobot/cli/internal/telemetry" @@ -68,8 +69,8 @@ Example: cmd.Flags().StringVar(&mode, "mode", "", "Pipeline mode: draft or locked") cmd.Flags().StringVar(&search, "search", "", "Filter pipelines by name substring") - cmd.Flags().IntVar(&offset, "offset", 0, "Pagination offset") - cmd.Flags().IntVar(&limit, "limit", 50, "Pagination limit (1-200)") + cmd.Flags().Var(countflags.NonNegativeInt(&offset, 0), "offset", "Pagination offset") + cmd.Flags().Var(countflags.PositiveInt(&limit, 50), "limit", "Pagination limit (1-200)") telemetry.TrackWith(cmd, func(_ *cobra.Command, _ []string) map[string]any { return map[string]any{ diff --git a/cmd/pipeline/list/cmd_test.go b/cmd/pipeline/list/cmd_test.go index 17a94f9c2..80447cbd7 100644 --- a/cmd/pipeline/list/cmd_test.go +++ b/cmd/pipeline/list/cmd_test.go @@ -144,3 +144,28 @@ func TestCmd_HasExpectedFlags(t *testing.T) { assert.NotNilf(t, flag, "expected --%s flag to be registered", name) } } + +func TestCmd_RejectsInvalidLimit(t *testing.T) { + // Rejected at parse time by countflags.PositiveInt, before any request. + cmd := Cmd() + cmd.SetArgs([]string{"--limit", "0"}) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.PreRunE = nil + + err := cmd.Execute() + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a positive integer") +} + +func TestCmd_RejectsInvalidOffset(t *testing.T) { + cmd := Cmd() + cmd.SetArgs([]string{"--offset", "-1"}) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.PreRunE = nil + + err := cmd.Execute() + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a non-negative integer") +} diff --git a/cmd/pipeline/run/list/cmd.go b/cmd/pipeline/run/list/cmd.go index d91992482..7cc6a3c2e 100644 --- a/cmd/pipeline/run/list/cmd.go +++ b/cmd/pipeline/run/list/cmd.go @@ -20,6 +20,7 @@ import ( "github.com/datarobot/cli/cmd/internal/errmsg" "github.com/datarobot/cli/cmd/pipeline/scopeflag" "github.com/datarobot/cli/internal/auth" + "github.com/datarobot/cli/internal/countflags" "github.com/datarobot/cli/internal/outputformat" "github.com/datarobot/cli/internal/pipeline" "github.com/datarobot/cli/internal/telemetry" @@ -66,8 +67,8 @@ Example: flags.Bind(cmd) _ = cmd.MarkFlagRequired("pipeline") - cmd.Flags().IntVar(&offset, "offset", 0, "Pagination offset") - cmd.Flags().IntVar(&limit, "limit", 100, "Maximum number of runs to return") + cmd.Flags().Var(countflags.NonNegativeInt(&offset, 0), "offset", "Pagination offset") + cmd.Flags().Var(countflags.PositiveInt(&limit, 100), "limit", "Maximum number of runs to return") telemetry.TrackWith(cmd, func(_ *cobra.Command, _ []string) map[string]any { return map[string]any{ diff --git a/cmd/pipeline/run/list/cmd_test.go b/cmd/pipeline/run/list/cmd_test.go index c39ace176..ee9b3a67e 100644 --- a/cmd/pipeline/run/list/cmd_test.go +++ b/cmd/pipeline/run/list/cmd_test.go @@ -59,3 +59,16 @@ func TestCmd_HasExpectedFlags(t *testing.T) { assert.NotNilf(t, cmd.Flags().Lookup(name), "expected --%s flag", name) } } + +func TestCmd_RejectsInvalidLimit(t *testing.T) { + // Rejected at parse time by countflags.PositiveInt, before any request. + err := runCmd(t, "--pipeline", "p", "--limit", "0") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a positive integer") +} + +func TestCmd_RejectsInvalidOffset(t *testing.T) { + err := runCmd(t, "--pipeline", "p", "--offset", "-1") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a non-negative integer") +} diff --git a/cmd/pipeline/run/task/logs/cmd.go b/cmd/pipeline/run/task/logs/cmd.go index 3f4c86645..bbfc99fc1 100644 --- a/cmd/pipeline/run/task/logs/cmd.go +++ b/cmd/pipeline/run/task/logs/cmd.go @@ -21,6 +21,7 @@ import ( "github.com/datarobot/cli/cmd/internal/errmsg" "github.com/datarobot/cli/internal/auth" + "github.com/datarobot/cli/internal/countflags" "github.com/datarobot/cli/internal/outputformat" "github.com/datarobot/cli/internal/pipeline" "github.com/datarobot/cli/internal/telemetry" @@ -100,7 +101,7 @@ Example: cmd.Flags().StringVar(&runID, "run", "", "Run (dispatch) ID") _ = cmd.MarkFlagRequired("run") cmd.Flags().StringVar(&stream, "stream", "", "Read durable S3 log: stdout or stderr") - cmd.Flags().IntVar(&tailLines, "tail", 0, "Limit to last N lines (live logs only)") + cmd.Flags().Var(countflags.NonNegativeInt(&tailLines, 0), "tail", "Limit to last N lines (live logs only)") cmd.Flags().IntVar(&nodeID, "node-id", 0, "Select a specific fan-out invocation by its nodeId (from `task list`)") cmd.Flags().StringVar(&verbosity, "verbosity", "", "Log verbosity: user (default) or all") diff --git a/cmd/pipeline/run/task/logs/cmd_test.go b/cmd/pipeline/run/task/logs/cmd_test.go index e91af45ab..9b48b2a7a 100644 --- a/cmd/pipeline/run/task/logs/cmd_test.go +++ b/cmd/pipeline/run/task/logs/cmd_test.go @@ -70,3 +70,11 @@ func TestCmd_HasExpectedFlags(t *testing.T) { assert.NotNilf(t, cmd.Flags().Lookup(name), "expected --%s flag", name) } } + +func TestCmd_RejectsNegativeTail(t *testing.T) { + // --tail 0 stays valid (no limit); negatives die at parse time via + // countflags.NonNegativeInt, before any request is made. + err := runCmd(t, "--pipeline", "p-1", "--run", "d-1", "--tail", "-1", "1") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a non-negative integer") +} diff --git a/cmd/pipeline/schedule/list/cmd.go b/cmd/pipeline/schedule/list/cmd.go index a1e6dd1c1..d820e83a0 100644 --- a/cmd/pipeline/schedule/list/cmd.go +++ b/cmd/pipeline/schedule/list/cmd.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/datarobot/cli/internal/auth" + "github.com/datarobot/cli/internal/countflags" "github.com/datarobot/cli/internal/outputformat" "github.com/datarobot/cli/internal/pipeline" "github.com/datarobot/cli/internal/telemetry" @@ -59,8 +60,8 @@ Example: cmd.Flags().StringVar(&pipelineID, "pipeline", "", "Pipeline ID") _ = cmd.MarkFlagRequired("pipeline") - cmd.Flags().IntVar(&offset, "offset", 0, "Pagination offset") - cmd.Flags().IntVar(&limit, "limit", 100, "Maximum number of schedules to return") + cmd.Flags().Var(countflags.NonNegativeInt(&offset, 0), "offset", "Pagination offset") + cmd.Flags().Var(countflags.PositiveInt(&limit, 100), "limit", "Maximum number of schedules to return") telemetry.TrackWith(cmd, func(_ *cobra.Command, _ []string) map[string]any { return map[string]any{ diff --git a/cmd/pipeline/schedule/list/cmd_test.go b/cmd/pipeline/schedule/list/cmd_test.go index eafff3495..eccfe3b0c 100644 --- a/cmd/pipeline/schedule/list/cmd_test.go +++ b/cmd/pipeline/schedule/list/cmd_test.go @@ -55,3 +55,16 @@ func TestCmd_HasExpectedFlags(t *testing.T) { assert.Nil(t, cmd.Flags().Lookup("version"), "unexpected --version flag after removal") } + +func TestCmd_RejectsInvalidLimit(t *testing.T) { + // Rejected at parse time by countflags.PositiveInt, before any request. + err := runCmd(t, "--pipeline", "p", "--limit", "0") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a positive integer") +} + +func TestCmd_RejectsInvalidOffset(t *testing.T) { + err := runCmd(t, "--pipeline", "p", "--offset", "-1") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a non-negative integer") +} diff --git a/cmd/pipeline/version/list/cmd.go b/cmd/pipeline/version/list/cmd.go index ef00ab033..484cda09a 100644 --- a/cmd/pipeline/version/list/cmd.go +++ b/cmd/pipeline/version/list/cmd.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/datarobot/cli/internal/auth" + "github.com/datarobot/cli/internal/countflags" "github.com/datarobot/cli/internal/outputformat" "github.com/datarobot/cli/internal/pipeline" "github.com/datarobot/cli/internal/telemetry" @@ -59,8 +60,8 @@ Example: cmd.Flags().StringVar(&pipelineID, "pipeline", "", "Pipeline ID") _ = cmd.MarkFlagRequired("pipeline") - cmd.Flags().IntVar(&offset, "offset", 0, "Pagination offset") - cmd.Flags().IntVar(&limit, "limit", 100, "Maximum number of versions to return") + cmd.Flags().Var(countflags.NonNegativeInt(&offset, 0), "offset", "Pagination offset") + cmd.Flags().Var(countflags.PositiveInt(&limit, 100), "limit", "Maximum number of versions to return") telemetry.TrackWith(cmd, func(_ *cobra.Command, _ []string) map[string]any { return map[string]any{ diff --git a/cmd/pipeline/version/list/cmd_test.go b/cmd/pipeline/version/list/cmd_test.go index fbb487c5e..96e044d30 100644 --- a/cmd/pipeline/version/list/cmd_test.go +++ b/cmd/pipeline/version/list/cmd_test.go @@ -53,3 +53,16 @@ func TestCmd_HasExpectedFlags(t *testing.T) { assert.NotNilf(t, cmd.Flags().Lookup(name), "expected --%s flag", name) } } + +func TestCmd_RejectsInvalidLimit(t *testing.T) { + // Rejected at parse time by countflags.PositiveInt, before any request. + err := runCmd(t, "--pipeline", "p", "--limit", "0") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a positive integer") +} + +func TestCmd_RejectsInvalidOffset(t *testing.T) { + err := runCmd(t, "--pipeline", "p", "--offset", "-1") + require.Error(t, err) + assert.Contains(t, err.Error(), "must be a non-negative integer") +}