Skip to content

fix(config-validate): emit structured output for -o json/yaml on success#56

Closed
shreemaan-abhishek wants to merge 1 commit into
masterfrom
fix/config-validate-output-format
Closed

fix(config-validate): emit structured output for -o json/yaml on success#56
shreemaan-abhishek wants to merge 1 commit into
masterfrom
fix/config-validate-output-format

Conversation

@shreemaan-abhishek
Copy link
Copy Markdown
Contributor

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

Summary

  • a7 config validate -f <file> -o json previously printed the human-readable string Config is valid and exited 0 regardless of the requested format, so pipelines like ... -o json | jq '.valid' failed with a parse error.
  • Read the inherited --output flag in RunE; on the success path emit validateResult{Valid: true, Message: "Config is valid"} via the shared cmdutil exporter for json/yaml.
  • Table / unset output is unchanged, so interactive users still see the friendly message.

How the bug was found

Surfaced by a new combinatorial/permutation test suite (separate follow-up PR). One of the 290+ cases asserted that -o json produces parseable JSON on success; the case failed with `stdout is not valid JSON: invalid character 'C'` (`'C'` is the first byte of "Config is valid").

Test plan

  • `go test ./pkg/cmd/config/validate/...` passes locally
  • New `TestConfigValidate_SuccessOutputJSON` parses stdout via `json.Unmarshal` and asserts `Valid == true`
  • New `TestConfigValidate_SuccessOutputYAML` asserts `valid: true` and the message appear in stdout
  • New `TestConfigValidate_SuccessDefaultOutput` keeps the existing "Config is valid" line for interactive use

Output shape

```json
{
"valid": true,
"message": "Config is valid"
}
```

```yaml
valid: true
message: Config is valid
```

The error path (validation failed) is unchanged — it still returns a non-zero exit code with the existing `config validation failed:` message on stderr. Structured error output is a separate concern not in scope here.

Summary by CodeRabbit

  • New Features

    • Config validation now supports structured output formats via the new -o/--output flag. Users can specify --output json or --output yaml to receive validation results in machine-readable formats with status and message fields.
  • Tests

    • Added test coverage for structured output behavior across different output formats.

Review Change Stack

`a7 config validate -f <file> -o json` previously printed the human-readable
string `Config is valid` on stdout and exited 0, ignoring the requested
format. Pipelines like `... -o json | jq '.valid'` failed with a parse
error.

Read the inherited --output flag in RunE; on the success path, emit a
`validateResult{Valid: true, Message: "Config is valid"}` via the shared
cmdutil exporter for json/yaml. Table / unset output is unchanged, so
interactive users keep seeing the friendly message.

Adds three unit tests covering json, yaml, and the default path.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

The PR adds structured output support to the validate command via a new -o/--output flag. The Options struct gains File and Output fields, a validateResult type encodes the validation status, and validateRun conditionally exports JSON or YAML results instead of printing plaintext based on the flag value. Tests verify success cases across all three output formats.

Changes

Add structured output format support to validate command

Layer / File(s) Summary
Output format contract and flag setup
pkg/cmd/config/validate/validate.go
Options struct gains File and Output fields to capture config path and requested format. validateResult type with JSON/YAML struct tags is defined. NewCmdValidate populates opts.Output from the -o flag during command execution.
Export implementation in validateRun
pkg/cmd/config/validate/validate.go
Success path checks opts.Output and calls cmdutil.NewExporter to emit structured validateResult{Valid: true, Message: "Config is valid"} for json/yaml formats; otherwise prints plaintext.
Test coverage for output formats
pkg/cmd/config/validate/validate_test.go
Test imports updated for JSON and Cobra support. New helper newCmdWithOutputFlag wraps command under a parent with persistent --output/-o flag. Three tests added: TestConfigValidate_SuccessOutputJSON validates JSON unmarshaling, TestConfigValidate_SuccessOutputYAML validates YAML structure, and TestConfigValidate_SuccessDefaultOutput validates plaintext output.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning Missing critical test for invalid output format; code silently falls back instead of erroring. Also line 48 discards GetString() error without checking. Add test for unsupported output format; handle flag error at line 48; add switch default case rejecting invalid formats instead of silently falling back.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding structured output support for JSON and YAML formats in the config validate command on success.
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.
Security Check ✅ Passed No security vulnerabilities found. validateResult struct outputs only valid bool and message string without exposing sensitive config data, tokens, or secrets.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/config-validate-output-format

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: 2

🤖 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/config/validate/validate.go`:
- Around line 89-95: The switch on opts.Output in the validate flow currently
only handles "json" and "yaml" and silently falls back for unknown non-empty
values; update the logic around opts.Output (the switch and its default path) to
detect unsupported non-empty outputs and return a non-nil error (e.g., using
fmt.Errorf) instead of proceeding to plaintext success, so callers relying on
structured output get a clear failure; keep using
cmdutil.NewExporter(opts.Output, opts.IO.Out).Write(validateResult{...}) for the
supported "json"/"yaml" branches and ensure validateResult remains used for the
successful export.
- Line 48: Change the code that reads the --output flag to handle and return the
error from c.Flags().GetString("output") instead of discarding it (assign to
opts.Output and check err, returning err up the call chain). After reading
opts.Output validate its value: if opts.Output is non-empty, either
construct/validate via cmdutil.NewExporter(opts.Output) (and return its error)
or explicitly reject values other than "json" or "yaml" by returning an error
like "unsupported output format"; ensure the function (the validate command
handler where opts.Output is used) returns that error rather than silently
falling back to the human message.
🪄 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: fdbcc3ec-bae5-4a0c-b9f1-e98d9468c2e4

📥 Commits

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

📒 Files selected for processing (2)
  • pkg/cmd/config/validate/validate.go
  • pkg/cmd/config/validate/validate_test.go

if opts.File == "" {
return &cmdutil.FlagError{Err: fmt.Errorf("required flag \"file\" not set")}
}
opts.Output, _ = c.Flags().GetString("output")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="pkg/cmd/config/validate/validate.go"
echo "== file =="
wc -l "$file"
echo

echo "== lines 1-140 (for context) =="
sed -n '1,140p' "$file" | nl -ba

echo
echo "== lines around 35-60 =="
sed -n '35,70p' "$file" | nl -ba

echo
echo "== lines around 80-110 =="
sed -n '80,120p' "$file" | nl -ba

echo
echo "== find where --output/-o is defined (c.Flags().GetString(\"output\")) =="
rg -n "GetString\\(\"output\"\\)" pkg/cmd/config/validate/validate.go pkg/cmd/config/validate -S || true

rg -n "persistent.*--output|persistent.*output|--output\"|StringVarP\\(|String\\(\"output\"" pkg/cmd/config -S || true

echo
echo "== find validate command wiring =="
rg -n "NewCmd\\(|validate" pkg/cmd/config/validate/validate.go -S || true

Repository: api7/a7

Length of output: 197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="pkg/cmd/config/validate/validate.go"

echo "== file =="
wc -l "$file"
echo

echo "== lines 1-140 =="
sed -n '1,140p' "$file" | cat -n
echo

echo "== lines 35-70 =="
sed -n '35,70p' "$file" | cat -n
echo

echo "== lines 80-120 =="
sed -n '80,120p' "$file" | cat -n
echo

echo "== find GetString(\"output\") in this file/folder =="
rg -n "GetString\\(\"output\"\\)" pkg/cmd/config/validate/validate.go pkg/cmd/config/validate -S || true
echo

echo "== find where --output/-o is defined elsewhere =="
rg -n "persistent.*--output|persistent.*output|--output\"|StringVarP\\(|String\\(\"output\"" pkg/cmd/config -S || true
echo

echo "== validate command wiring =="
rg -n "NewCmd\\(|validate" pkg/cmd/config/validate/validate.go -S || true

Repository: api7/a7

Length of output: 9674


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== rg where output flag is defined across pkg/cmd =="

rg -n "StringVarP\\(&[^\\n]*opts\\.Output[^\\n]*,\\s*\"output\"\\s*,\\s*\"o\"" pkg/cmd -S || true
rg -n "StringVarP\\(&[^\\n]*,\\s*\"output\"\\s*,\\s*\"o\"" pkg/cmd -S || true
rg -n "Persistent.*StringVarP\\(.*\"output\".*\"o\"" pkg/cmd -S || true
rg -n "Persistent.*--output|--output\\b|\"output\"\\s*,\\s*\"o\"" pkg/cmd -S || true

echo
echo "== rg for GetString(\"output\") across pkg/cmd =="

rg -n "GetString\\(\"output\"\\)" pkg/cmd -S || true

Repository: api7/a7

Length of output: 8978


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate cmdutil.NewExporter implementation =="
fd -a "exporter" pkg/cmdutil pkg -t f || true
rg -n "func NewExporter" -S pkg/cmdutil pkg || true
rg -n "NewExporter\\(" -S pkg/cmdutil pkg/cmd || true

Repository: api7/a7

Length of output: 9085


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== root persistent --output definition =="
sed -n '1,120p' pkg/cmd/root/root.go | cat -n

echo
echo "== cmdutil/exporter.go =="
sed -n '1,220p' pkg/cmdutil/exporter.go | cat -n

echo
echo "== validate tests =="
ls -la pkg/cmd/config/validate || true
sed -n '1,200p' pkg/cmd/config/validate/validate_test.go | cat -n

Repository: api7/a7

Length of output: 13353


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "newCmdWithOutputFlag\\(" pkg/cmd/config/validate/validate_test.go
rg -n "NewCmdValidate\\(" pkg/cmd/config/validate/validate_test.go

echo
echo "== show remaining part of validate_test.go around newCmdWithOutputFlag usage =="
sed -n '1,120p' pkg/cmd/config/validate/validate_test.go | cat -n
sed -n '120,260p' pkg/cmd/config/validate/validate_test.go | cat -n
sed -n '260,520p' pkg/cmd/config/validate/validate_test.go | cat -n

Repository: api7/a7

Length of output: 15736


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== validate.go output handling =="
rg -n "switch opts\\.Output|case \"json\"|case \"yaml\"|fmt\\.Fprintln\\(opts\\.IO\\.Out" pkg/cmd/config/validate/validate.go
sed -n '80,110p' pkg/cmd/config/validate/validate.go | cat -n

echo
echo "== config/dump.go output handling =="
sed -n '1,140p' pkg/cmd/config/dump/dump.go | cat -n
rg -n "opts\\.Output|NewExporter\\(" pkg/cmd/config/dump/dump.go

echo
echo "== config/diff.go output handling =="
sed -n '1,140p' pkg/cmd/config/diff/diff.go | cat -n
rg -n "opts\\.Output|switch opts\\.Output|NewExporter\\(" pkg/cmd/config/diff/diff.go

echo
echo "== other commands: how they handle invalid opts.Output =="
rg -n "switch opts\\.Output" pkg/cmd -S --glob='*.go'
rg -n "case \"json\", \"yaml\"|case \"json\".*case \"yaml\"" pkg/cmd -S --glob='*.go'

Repository: api7/a7

Length of output: 6821


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "output.*(invalid|unsupported|toml|table|xml|yaml|json)" pkg/cmd/config/validate/validate_test.go -S || true
rg -n "\"-o\"|\"--output\"" pkg/cmd/config/validate/validate_test.go -S || true
rg -n "SuccessOutput|Output" pkg/cmd/config/validate/validate_test.go -S || true

Repository: api7/a7

Length of output: 1446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  pkg/cmd/context/list/list.go \
  pkg/cmd/stream-route/list/list.go \
  pkg/cmd/service/list/list.go \
  pkg/cmd/route/list/list.go
do
  echo "== $f =="
  rg -n "GetString\\(\"output\"\\)" "$f" -n || true
  # show nearby output handling
  # print switch/if on opts.Output if present
  rg -n "switch opts\\.Output|if opts\\.Output|case \"json\"|case \"yaml\"" "$f" -S || true
  sed -n '1,140p' "$f" | cat -n
  echo
done

Repository: api7/a7

Length of output: 15128


Handle --output flag error and reject unsupported output values in config validate

  • Line 48: opts.Output, _ = c.Flags().GetString("output") discards the returned error; return it instead to follow the “never suppress errors” rule.
  • Lines 89-97: if opts.Output is non-empty but not json/yaml, the command silently falls back to printing the human message ("Config is valid"), instead of erroring (in contrast to other commands that route through cmdutil.NewExporter, which returns unsupported output format).
Proposed fix
-			opts.Output, _ = c.Flags().GetString("output")
+			output, err := c.Flags().GetString("output")
+			if err != nil {
+				return fmt.Errorf("failed to read output flag: %w", err)
+			}
+			opts.Output = output
🤖 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/config/validate/validate.go` at line 48, Change the code that reads
the --output flag to handle and return the error from
c.Flags().GetString("output") instead of discarding it (assign to opts.Output
and check err, returning err up the call chain). After reading opts.Output
validate its value: if opts.Output is non-empty, either construct/validate via
cmdutil.NewExporter(opts.Output) (and return its error) or explicitly reject
values other than "json" or "yaml" by returning an error like "unsupported
output format"; ensure the function (the validate command handler where
opts.Output is used) returns that error rather than silently falling back to the
human message.

Comment on lines +89 to +95
switch opts.Output {
case "json", "yaml":
return cmdutil.NewExporter(opts.Output, opts.IO.Out).Write(validateResult{
Valid: true,
Message: "Config is valid",
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return an error for unsupported non-empty output values (Lines 89-95).

Right now unknown values (e.g. -o jsn) silently fall back to plaintext success, which can break automation expecting structured output.

Proposed fix
 	switch opts.Output {
 	case "json", "yaml":
 		return cmdutil.NewExporter(opts.Output, opts.IO.Out).Write(validateResult{
 			Valid:   true,
 			Message: "Config is valid",
 		})
+	case "", "table":
+		fmt.Fprintln(opts.IO.Out, "Config is valid")
+		return nil
+	default:
+		return fmt.Errorf("unsupported output format: %s", opts.Output)
 	}
-	fmt.Fprintln(opts.IO.Out, "Config is valid")
-	return nil
🤖 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/config/validate/validate.go` around lines 89 - 95, The switch on
opts.Output in the validate flow currently only handles "json" and "yaml" and
silently falls back for unknown non-empty values; update the logic around
opts.Output (the switch and its default path) to detect unsupported non-empty
outputs and return a non-nil error (e.g., using fmt.Errorf) instead of
proceeding to plaintext success, so callers relying on structured output get a
clear failure; keep using cmdutil.NewExporter(opts.Output,
opts.IO.Out).Write(validateResult{...}) for the supported "json"/"yaml" branches
and ensure validateResult remains used for the successful export.

@shreemaan-abhishek
Copy link
Copy Markdown
Contributor Author

I think it's alright if -o json or -o yaml does not work here, maybe we can do this sometime in future.

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.
@shreemaan-abhishek shreemaan-abhishek review requested due to automatic review settings May 27, 2026 08:21
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.

1 participant