Skip to content

fix(cmdutil): standardize -o output-format dispatch across list commands#57

Merged
shreemaan-abhishek merged 1 commit into
masterfrom
fix/list-output-format-consistency
May 27, 2026
Merged

fix(cmdutil): standardize -o output-format dispatch across list commands#57
shreemaan-abhishek merged 1 commit into
masterfrom
fix/list-output-format-consistency

Conversation

@shreemaan-abhishek
Copy link
Copy Markdown
Contributor

@shreemaan-abhishek shreemaan-abhishek commented May 27, 2026

Summary

Two inconsistencies in how list commands handled the global `--output` flag, both surfaced by the new combinatorial permutation suite (separate follow-up PR):

  1. `a7 gateway-group list -o totally-not-a-format` silently accepted any value and fell back to table. Typos like `-o jzon` gave no feedback. `plugin list` had the same problem.
  2. `a7 service list -o table` was explicitly rejected with `Error: unsupported output format: table`, even though `table` is the documented default. 9 other list commands had this same bug.

Approach

Centralize the dispatch in `cmdutil`:

  • `cmdutil.ValidateOutputFormat` accepts `{empty, table, json, yaml}` and rejects anything else with a clear message listing the valid set.
  • `cmdutil.IsStructuredOutput` returns true only for `json`/`yaml` so each list command can choose between the structured exporter and its own table renderer.

Every list command (12 files: consumer, context, credential, gateway-group, global-rule, plugin, proto, route, secret, service, ssl, stream-route) now:

  • Validates `--output` at the start of `RunE` (fails fast on typos).
  • Dispatches via `cmdutil.IsStructuredOutput(opts.Output)` so explicit `-o table` falls through to the table renderer like the empty default.

Behavior matrix

Command Before After
`-o table` (any list) service/route/etc: error; gateway-group/plugin: table table for all
`-o jzon` (typo) gateway-group: silent table; service: error with confusing message clear error listing valid formats
`-o yaml`/`-o json` works unchanged
no `-o` table unchanged

Test plan

  • `go test ./pkg/cmd/... ./pkg/cmdutil/... -count=1` passes locally
  • New `TestValidateOutputFormat` covers accepted + rejected values
  • New `TestIsStructuredOutput` covers all four shapes
  • Existing list-command unit tests untouched and still pass
  • Live-verified against a local API7 EE 3.9 instance:
    • `gateway-group list -o totally-not-a-format` -> `Error: unsupported output format: "totally-not-a-format" (valid: table, json, yaml)` (exit 1)
    • `service list -o table` -> prints the table header
    • `gateway-group list -o yaml` -> emits YAML (sanity)

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced --output flag validation across all list commands to reject invalid formats with clear error messages early in execution.
    • Standardized output format detection to ensure structured formats (JSON/YAML) are properly recognized and applied consistently.

Review Change Stack

Two inconsistencies in how list commands handle the global --output flag:

1. `a7 gateway-group list -o totally-not-a-format` accepted any value and
   silently fell back to table rendering. Typos (e.g. `-o jzon`) gave no
   feedback. `plugin list` had the same problem.

2. `a7 service list -o table` rejected the explicit value with
   `Error: unsupported output format: table`, even though `table` is the
   documented default and works when -o is omitted. Every list command
   except gateway-group and plugin had this problem (9 commands).

Centralize the dispatch in cmdutil:

- `cmdutil.ValidateOutputFormat` accepts {empty, table, json, yaml} and
  rejects anything else with a message listing the valid set.
- `cmdutil.IsStructuredOutput` returns true only for json/yaml so each
  list command can decide between exporter and table renderer.

Every list command (12 files: consumer, context, credential, gateway-group,
global-rule, plugin, proto, route, secret, service, ssl, stream-route) now:

- Validates --output at the start of RunE (fails fast on typos).
- Dispatches via `cmdutil.IsStructuredOutput(opts.Output)` so explicit
  `-o table` falls through to the table renderer like the empty default.

Adds unit tests for both helpers. Existing tests continue to pass.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

This PR adds centralized output format validation and detection logic, then applies it uniformly across twelve list subcommands. Two new utility functions in cmdutil replace scattered format checks with consistent behavior: ValidateOutputFormat rejects invalid formats early, and IsStructuredOutput identifies which formats require structured (JSON/YAML) output.

Changes

Output format validation and refactoring

Layer / File(s) Summary
Output format validation and structured output detection helpers
pkg/cmdutil/exporter.go, pkg/cmdutil/exporter_test.go
Added ValidateOutputFormat to validate --output values against supported formats (empty, table, json, yaml) and IsStructuredOutput to classify formats as structured or not. Tests verify valid format acceptance, invalid format rejection with descriptive errors, and structured output detection logic.
Apply format validation and structured output refactoring across list commands
pkg/cmd/consumer/list/list.go, pkg/cmd/context/list/list.go, pkg/cmd/credential/list/list.go, pkg/cmd/gateway-group/list/list.go, pkg/cmd/global-rule/list/list.go, pkg/cmd/plugin/list/list.go, pkg/cmd/proto/list/list.go, pkg/cmd/route/list/list.go, pkg/cmd/secret/list/list.go, pkg/cmd/service/list/list.go, pkg/cmd/ssl/list/list.go, pkg/cmd/stream-route/list/list.go
Each list command now validates its output format early in RunE using ValidateOutputFormat, returning an error for invalid formats. Output branching logic was refactored to use cmdutil.IsStructuredOutput(opts.Output) instead of checking whether the format string is non-empty, ensuring structured exporters are used only for json/yaml formats.

🎯 2 (Simple) | ⏱️ ~12 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Check ❌ Error PR introduces credential command with unredacted plugin data exposure in JSON/YAML output, lacking RedactCredentials function that exists for secrets/SSL. Add RedactCredentials() function to types_credential.go; use it when exporting credentials via NewExporter in credential list/get commands.
E2e Test Quality Review ⚠️ Warning PR lacks E2E test coverage for output-format validation; no tests verify invalid formats like -o jzon are rejected. Error handling in 12 list commands ignores GetString() errors. Add E2E tests for invalid output format rejection. Fix GetString() error handling in all list commands instead of using _ placeholder.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: standardizing output-format dispatch logic across list commands using centralized helpers in cmdutil.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/list-output-format-consistency

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/cmd/gateway-group/list/list.go (1)

72-79: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply label=value filtering before the structured-output return.

Line 72 returns early for JSON/YAML, so the value-side label filter (Line 86–Line 94) is skipped. --label key=value -o json|yaml currently returns unfiltered-by-value results.

Proposed fix
-	if cmdutil.IsStructuredOutput(opts.Output) {
-		exp := cmdutil.NewExporter(opts.Output, opts.IO.Out)
-		var result api.ListResponse[api.GatewayGroup]
-		if err := json.Unmarshal(body, &result); err != nil {
-			return err
-		}
-		return exp.Write(result.List)
-	}
-
 	var result api.ListResponse[api.GatewayGroup]
 	if err := json.Unmarshal(body, &result); err != nil {
 		return err
 	}
 
 	if labelValue != "" {
 		filtered := make([]api.GatewayGroup, 0)
 		for _, item := range result.List {
 			if item.Labels != nil && item.Labels[labelKey] == labelValue {
 				filtered = append(filtered, item)
 			}
 		}
 		result.List = filtered
 	}
+
+	if cmdutil.IsStructuredOutput(opts.Output) {
+		exp := cmdutil.NewExporter(opts.Output, opts.IO.Out)
+		return exp.Write(result.List)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cmd/gateway-group/list/list.go` around lines 72 - 79, The
structured-output branch returns early after unmarshalling (the block using
cmdutil.IsStructuredOutput, cmdutil.NewExporter, json.Unmarshal and exp.Write),
which skips the value-side label filtering logic later; move or replicate the
label=value filtering to run immediately after json.Unmarshal (before exp.Write)
so the exported result.List is filtered the same way as the non-structured
path—apply the same predicate used in the existing label-filtering code (the
logic around the value-side label filter currently at lines 86–94) to
result.List before calling exp.Write, or extract that predicate into a helper
and reuse it here.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/cmd/consumer/list/list.go`:
- Around line 42-47: The calls to c.Flags().GetString for opts.Output,
opts.GatewayGroup and opts.Label currently ignore errors; update the code in the
function that sets these fields so you capture and handle the returned error
from each GetString call (e.g., err := c.Flags().GetString("output"); if err !=
nil { return err }) before calling cmdutil.ValidateOutputFormat(opts.Output),
and likewise for "gateway-group" and "label" — return or propagate any flag
parsing error instead of discarding it to avoid silently using zero values.

---

Outside diff comments:
In `@pkg/cmd/gateway-group/list/list.go`:
- Around line 72-79: The structured-output branch returns early after
unmarshalling (the block using cmdutil.IsStructuredOutput, cmdutil.NewExporter,
json.Unmarshal and exp.Write), which skips the value-side label filtering logic
later; move or replicate the label=value filtering to run immediately after
json.Unmarshal (before exp.Write) so the exported result.List is filtered the
same way as the non-structured path—apply the same predicate used in the
existing label-filtering code (the logic around the value-side label filter
currently at lines 86–94) to result.List before calling exp.Write, or extract
that predicate into a helper and reuse it here.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86d82dbf-9b8b-4ec5-a920-e214e08cce5c

📥 Commits

Reviewing files that changed from the base of the PR and between e97dda6 and c617fe3.

📒 Files selected for processing (14)
  • pkg/cmd/consumer/list/list.go
  • pkg/cmd/context/list/list.go
  • pkg/cmd/credential/list/list.go
  • pkg/cmd/gateway-group/list/list.go
  • pkg/cmd/global-rule/list/list.go
  • pkg/cmd/plugin/list/list.go
  • pkg/cmd/proto/list/list.go
  • pkg/cmd/route/list/list.go
  • pkg/cmd/secret/list/list.go
  • pkg/cmd/service/list/list.go
  • pkg/cmd/ssl/list/list.go
  • pkg/cmd/stream-route/list/list.go
  • pkg/cmdutil/exporter.go
  • pkg/cmdutil/exporter_test.go

Comment on lines 42 to 47
opts.Output, _ = c.Flags().GetString("output")
if err := cmdutil.ValidateOutputFormat(opts.Output); err != nil {
return err
}
opts.GatewayGroup, _ = c.Flags().GetString("gateway-group")
opts.Label, _ = c.Flags().GetString("label")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Handle flag parsing errors instead of discarding them.

Line 42, Line 46, and Line 47 ignore GetString errors. If flag registration changes, this silently falls back to zero values and masks the root cause.

Proposed fix
-			opts.Output, _ = c.Flags().GetString("output")
+			var err error
+			opts.Output, err = c.Flags().GetString("output")
+			if err != nil {
+				return err
+			}
 			if err := cmdutil.ValidateOutputFormat(opts.Output); err != nil {
 				return err
 			}
-			opts.GatewayGroup, _ = c.Flags().GetString("gateway-group")
-			opts.Label, _ = c.Flags().GetString("label")
+			opts.GatewayGroup, err = c.Flags().GetString("gateway-group")
+			if err != nil {
+				return err
+			}
+			opts.Label, err = c.Flags().GetString("label")
+			if err != nil {
+				return err
+			}
 			return actionRun(opts)

As per coding guidelines, **/*.go: Never suppress errors. Always handle and propagate errors explicitly; and **/*.{js,ts,tsx,go}: every function return value must be checked for errors when applicable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cmd/consumer/list/list.go` around lines 42 - 47, The calls to
c.Flags().GetString for opts.Output, opts.GatewayGroup and opts.Label currently
ignore errors; update the code in the function that sets these fields so you
capture and handle the returned error from each GetString call (e.g., err :=
c.Flags().GetString("output"); if err != nil { return err }) before calling
cmdutil.ValidateOutputFormat(opts.Output), and likewise for "gateway-group" and
"label" — return or propagate any flag parsing error instead of discarding it to
avoid silently using zero values.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes --output handling for list commands by centralizing validation and structured-output detection in cmdutil.

Changes:

  • Adds ValidateOutputFormat and IsStructuredOutput helpers.
  • Updates list commands to reject invalid output formats early and treat explicit -o table as table output.
  • Adds unit coverage for the new cmdutil helpers.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pkg/cmdutil/exporter.go Adds shared output-format validation and structured-output detection helpers.
pkg/cmdutil/exporter_test.go Adds helper-level tests for valid/invalid formats and structured output detection.
pkg/cmd/consumer/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/context/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/credential/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/gateway-group/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/global-rule/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/plugin/list/list.go Uses shared validation and enables YAML structured output.
pkg/cmd/proto/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/route/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/secret/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/service/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/ssl/list/list.go Uses shared validation and structured-output dispatch.
pkg/cmd/stream-route/list/list.go Uses shared validation and structured-output dispatch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +65 to +95
func TestValidateOutputFormat(t *testing.T) {
for _, ok := range []string{"", "table", "json", "yaml"} {
if err := ValidateOutputFormat(ok); err != nil {
t.Errorf("expected %q to be accepted, got %v", ok, err)
}
}
for _, bad := range []string{"jzon", "yml", "TABLE", "csv", "totally-not-a-format"} {
err := ValidateOutputFormat(bad)
if err == nil {
t.Errorf("expected %q to be rejected", bad)
continue
}
if !strings.Contains(err.Error(), "valid: table, json, yaml") {
t.Errorf("error for %q should list the valid set, got: %v", bad, err)
}
}
}

func TestIsStructuredOutput(t *testing.T) {
cases := map[string]bool{
"": false,
"table": false,
"json": true,
"yaml": true,
}
for format, want := range cases {
if got := IsStructuredOutput(format); got != want {
t.Errorf("IsStructuredOutput(%q) = %v, want %v", format, got, want)
}
}
}
@shreemaan-abhishek shreemaan-abhishek merged commit addcf9a into master May 27, 2026
7 checks passed
shreemaan-abhishek added a commit that referenced this pull request May 27, 2026
…gaps

The first CI run on this branch surfaced two problems:

1. The suite ran inside the regular `make test-e2e` target. That target is
   the existing CI gate; bringing 290+ permutation cases into it both
   inflates the run time and tied PR merge to the suite reporting clean —
   which it does not yet, because three real CLI inconsistencies are still
   open (fixed in PR #56 and PR #57). The permutation matrix is meant to be
   a manual triage tool, not a CI gate.

   Fix: tag the Describe with Label("permutation") and update test-e2e /
   test-e2e-full to skip it via --label-filter='!permutation'. The dedicated
   test-e2e-permutation target inverts the filter to include only the
   permutation specs. Net effect: regular CI no longer sees the suite;
   `make test-e2e-permutation` is unchanged.

2. The CI EE rejected `stream-route create` with `Error: API error
   (status 400): can not create a Stream Route to the HTTP Service`. Stream
   routes need an L4 service on that deployment; my local EE accepted them
   attached to an HTTP service. The walker then cascaded 7 failures
   (get / list / export / delete all error with "resource not found").

   Fix: detect known capability-gap stderr patterns after the create step
   and downgrade the rest of the walker to a single informational "skipped"
   record. Patterns cover stream-route on HTTP-only EEs plus the existing
   secret-provider / vault gaps already handled by local_stability.

The walker now also stops short on any create failure (capability gap or
otherwise) rather than producing cascade noise from "resource not found"
on every follow-up step.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants