Skip to content

Issue #2973 Return structured JSON errors from config API#5143

Open
sebrandon1 wants to merge 1 commit intocrc-org:mainfrom
sebrandon1:structured-json-config-errors
Open

Issue #2973 Return structured JSON errors from config API#5143
sebrandon1 wants to merge 1 commit intocrc-org:mainfrom
sebrandon1:structured-json-config-errors

Conversation

@sebrandon1
Copy link
Copy Markdown
Contributor

@sebrandon1 sebrandon1 commented Feb 11, 2026

Summary

  • When setting config via the API with invalid values, errors were returned as concatenated plain text, making them unparseable by API clients (e.g. the Electron tray app)
  • Detect MultiError at the HTTP response layer and return a JSON array of structured ValidationError objects with Content-Type: application/json
  • Preserve error response body in the API client's sendRequest() via HTTPError (previously discarded)

Resolves #2973

Jira: https://issues.redhat.com/browse/CNFCERT-1343

Examples

Single invalid value:

curl -s -X POST -H 'Content-Type: application/json' \
  -d '{"properties":{"cpus":0}}' \
  http://localhost/api/config
[{"message":"Value '0' for configuration property 'cpus' is invalid, reason: requires CPUs >= 4"}]

Multiple invalid values:

curl -s -X POST -H 'Content-Type: application/json' \
  -d '{"properties":{"cpus":0,"memory":1}}' \
  http://localhost/api/config
[
  {"message":"Value '0' for configuration property 'cpus' is invalid, reason: requires CPUs >= 4"},
  {"message":"Value '1' for configuration property 'memory' is invalid, reason: requires memory in MiB >= 10752"}
]

Non-validation errors remain plain text (unchanged):

curl -s -X POST -H 'Content-Type: application/json' \
  -d 'not-json' http://localhost/api/config
# → invalid character 'o' in literal null (expecting 'u')

Test plan

  • Unit tests pass (go test -race --tags "build containers_image_openpgp" ./pkg/crc/api/...)
  • make test passes
  • make lint passes (0 issues)
  • Built binary, started daemon locally, verified JSON responses via curl
  • Verify Electron tray app can parse error responses

Summary by CodeRabbit

  • Bug Fixes

    • Validation errors now return all configuration issues together as a JSON array with clearer, per-property messages (e.g., CPU, memory).
    • Error responses from the server include returned error details so clients receive the response body for diagnostics and clearer status handling.
  • Tests

    • Added tests verifying multiple validation errors are returned and parsed correctly (422 responses with structured messages).

@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Feb 11, 2026

Hi @sebrandon1. Thanks for your PR.

I'm waiting for a crc-org member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-ci openshift-ci bot requested review from anjannath and gbraad February 11, 2026 14:21
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds structured validation error handling: server detects errors.MultiError and returns JSON array of client.ValidationError with HTTP 422; client sendRequest surfaces non-success bodies via HTTPError; tests updated/added to assert multiple validation errors are returned and JSON-decoded.

Changes

Cohort / File(s) Summary
Client types & request errors
pkg/crc/api/client/types.go, pkg/crc/api/client/client.go
Adds exported ValidationError (Message string JSON). sendRequest now reads non-success response bodies and returns an *HTTPError containing URL, Method, StatusCode, and Body (wraps body-read failures).
Server multi-error handling
pkg/crc/api/helpers.go
Detects errors.MultiError from handlers and adds writeMultiError to map non-nil underlying errors to client.ValidationError, JSON-encode the slice, set Content-Type: application/json, and respond with HTTP 422; logs encode failures.
Tests
pkg/crc/api/api_http_test.go
Extends test cases for POST /config and adds TestSetConfigMultipleErrors which posts invalid cpus and memory, expects HTTP 422, decodes response as []client.ValidationError, and asserts two validation messages (order-independent).
Imports / wiring
pkg/crc/api/...
Updates imports to include encoding/json, errors, and pkg/crc/api/client where required for new logic and tests.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant APIServer as API Server
    participant Helper as writeMultiError
    participant Encoder as JSON Encoder

    Client->>APIServer: POST /config (invalid fields)
    APIServer->>APIServer: validate -> returns errors.MultiError
    APIServer->>Helper: invoke writeMultiError(multiErr)
    Helper->>Helper: map each error -> client.ValidationError
    Helper->>Encoder: encode []client.ValidationError
    Encoder-->>Client: HTTP 422 + application/json body (array of validation errors)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I found two faults beneath the log,

Packed them neat in JSON fog,
Sent them back with a gentle thump,
Now clients parse instead of clump,
🥕 Hop, validate, and jog!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provided is comprehensive, including summary, examples, and test plan; however, the required template sections (Type of change, Proposed changes, Testing, Contribution Checklist) are not filled out. Complete the description template by filling in Type of change, Proposed changes, Testing details, and contribution checklist items to ensure consistency with repository standards.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Issue #2973 Return structured JSON errors from config API' directly reflects the main change: implementing structured JSON error responses for the config API.
Linked Issues check ✅ Passed The PR implements structured JSON error responses for validation failures (#2973), detecting MultiError and returning ValidationError objects with message fields as JSON arrays with 422 status codes.
Out of Scope Changes check ✅ Passed All changes are directly related to the objective of returning structured JSON errors: new ValidationError type, MultiError detection in helpers, HTTPError body preservation in client, and corresponding tests.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from b811b3f to 9581ba1 Compare February 12, 2026 16:21
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.

Caution

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

⚠️ Outside diff range comments (1)
pkg/crc/api/helpers.go (1)

124-127: ⚠️ Potential issue | 🟠 Major

Pre-existing bug: response headers are set after WriteHeader.

In Go's net/http, calling w.WriteHeader() sends the status line and headers immediately. Any w.Header().Set() calls after that are silently ignored. The new writeMultiError correctly sets headers before WriteHeader (lines 141–142), but the existing success path here does it in the wrong order.

Not introduced by this PR, but worth fixing for consistency — especially since the new code demonstrates the correct pattern.

Proposed fix
-	w.WriteHeader(c.code)
 	for k, v := range c.headers {
 		w.Header().Set(k, v)
 	}
+	w.WriteHeader(c.code)
🧹 Nitpick comments (1)
pkg/crc/api/client/types.go (1)

70-73: Consider enriching ValidationError with additional fields per issue requirements.

The linked issue #2973 calls for structured errors that include the property name, provided value, expected/default value, and reason — enabling clients to programmatically present specific field-level feedback. Currently, only Message is provided, which is essentially the same information as the plain-text error but wrapped in JSON.

Consider expanding the struct, e.g.:

type ValidationError struct {
	Property string      `json:"property"`
	Value    interface{} `json:"value,omitempty"`
	Reason   string      `json:"reason"`
}

This would give client applications (like the Electron tray app) something truly machine-parseable rather than requiring them to parse a human-readable message string.

@praveenkumar
Copy link
Copy Markdown
Member

We need to check this change with podman desktop which uses api endpoint for any operation for crc side.

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 9581ba1 to 2fd8b3b Compare February 17, 2026 15:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 17, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":401,"request":{"method":"PATCH","url":"https://api.github.com/repos/crc-org/crc/issues/comments/3884745511","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: failure by coderabbit.ai -->\n\n> [!CAUTION]\n> ## Review failed\n> \n> Failed to post review comments\n\n<!-- end of auto-generated comment: failure by coderabbit.ai -->\n\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nAdds server handling to serialize multiple validation errors into a JSON array, a new `client.ValidationError` type, client request error bodies wrapped as `HTTPError`, and tests asserting multiple validation errors are returned as JSON.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Client types & request errors** <br> `pkg/crc/api/client/types.go`, `pkg/crc/api/client/client.go`|Adds `ValidationError` struct (`Message string` JSON). `sendRequest` now reads non-success response bodies and returns a structured `HTTPError` containing URL, Method, StatusCode, and Body.|\n|**Server multi-error handling** <br> `pkg/crc/api/helpers.go`|Detects `errors.MultiError` from handlers and adds `writeMultiError` to map each underlying error to `client.ValidationError` and write JSON array with `Content-Type: application/json` and HTTP 500.|\n|**Tests (duplicate added)** <br> `pkg/crc/api/api_http_test.go`|Adds `TestSetConfigMultipleErrors` (duplicated implementation present) that posts invalid config, expects HTTP 500, parses response as `[]client.ValidationError`, and asserts two validation errors with expected messages.|\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    autonumber\n    participant Client\n    participant APIServer as API Server (handler)\n    participant Helper as writeMultiError\n    participant Encoder as JSON Encoder\n\n    Client->>APIServer: POST /config with invalid fields\n    APIServer->>APIServer: validate input -> returns errors.MultiError\n    APIServer->>Helper: pass MultiError\n    Helper->>Helper: map each error -> client.ValidationError\n    Helper->>Encoder: encode []client.ValidationError\n    Encoder-->>Client: HTTP 500 + application/json body (array of validation errors)\n```\n\n## Estimated code review effort\n\n🎯 3 (Moderate) | ⏱️ ~20 minutes\n\n## Poem\n\n> 🐇 I hopped through tests and tiny woes,  \n> > Found many errors in tidy rows,  \n> > Packaged each in JSON song,  \n> > Sent them back where they belong,  \n> > 🥕 The rabbit dances, code is strong!\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 2 | ❌ 1</summary>\n\n### ❌ Failed checks (1 warning)\n\n|     Check name     | Status     | Explanation                                                                          | Resolution                                                                         |\n| :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n<details>\n<summary>✅ Passed checks (2 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                                                           |\n| :---------------: | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                                           |\n|    Title check    | ✅ Passed | The PR title clearly and specifically summarizes the main change: returning structured JSON errors from the config API rather than plain text, which aligns with the core objective of the changeset. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- This is an auto-generated comment: resource warnings by coderabbit.ai -->\n\n> [!WARNING]\n> ## Review ran into problems\n> \n> <details>\n> <summary>🔥 Problems</summary>\n> \n> Git: Failed to clone repository. Please run the `@coderabbitai full review` command to re-trigger a full review. If the issue persists, set `path_filters` to include or exclude specific files.\n> \n> </details>\n\n<!-- end of auto-generated comment: resource warnings by coderabbit.ai -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=crc-org/crc&utm_content=5143)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":1}},"response":{"url":"https://api.github.com/repos/crc-org/crc/issues/comments/3884745511","status":401,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","connection":"close","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 17 Feb 2026 15:26:10 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"043E:51E49:30AA2B:D1642C:69948892","x-xss-protection":"0"},"data":{"message":"Bad credentials","documentation_url":"https://docs.github.com/rest","status":"401"}}}

@sebrandon1
Copy link
Copy Markdown
Contributor Author

@praveenkumar I tested this change against the crc-extension source to assess Podman Desktop compatibility. Here are the findings:

What changes: The POST /api/config endpoint now returns a JSON array [{"message":"..."}] with Content-Type: application/json instead of plain text when config validation fails.

How crc-extension handles it: In daemon-commander.ts:108, configSet does throw new Error(result.body) on non-200 responses. This means the raw JSON string would become the error message shown to users (e.g., in preferences.ts via showErrorMessage).

Impact: Not a crash — but users would see [{"message":"Value '0' for..."}] instead of the clean text message. Cosmetic degradation.

Test evidence:

$ curl -X POST --unix-socket ~/.crc/crc-http.sock http://localhost/api/config \
  -d '{"properties":{"cpus":0,"memory":1}}'

[{"message":"Value '0' for configuration property 'cpus' is invalid, reason: requires CPUs >= 4"},
 {"message":"Value '1' for configuration property 'memory' is invalid, reason: requires memory in MiB >= 10752"}]

Options to address:

  1. Update crc-extension to parse JSON error bodies (check Content-Type header or try JSON.parse)
  2. Add a human-readable Error() string alongside the structured JSON (e.g., wrap in {"error":"...","details":[...]})
  3. Only use JSON array format when client sends Accept: application/json, fall back to plain text otherwise

Let me know which approach you'd prefer and I can implement it.

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 2fd8b3b to 15a920f Compare February 24, 2026 15:21
@praveenkumar
Copy link
Copy Markdown
Member

/ok-to-test

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

🧹 Nitpick comments (2)
pkg/crc/api/client/types.go (1)

70-73: Consider expanding ValidationError to structured fields for true machine-readability.

The PR's linked issue (#2973) asks for a schema with discrete fields (property name, invalid value, required/default value, reason). The current single Message field still forces clients to do brittle string parsing — the same root problem as the plain-text concatenation it replaces.

♻️ Optional schema expansion
 // ValidationError represents a single validation error in a structured JSON error response.
 type ValidationError struct {
-    Message string `json:"message"`
+    Message  string `json:"message"`           // human-readable summary (kept for backwards compat)
+    Property string `json:"property,omitempty"` // the configuration key that failed validation
+    Value    string `json:"value,omitempty"`    // the value that was rejected
+    Reason   string `json:"reason,omitempty"`   // machine-readable reason for the failure
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/crc/api/client/types.go` around lines 70 - 73, The ValidationError struct
currently only has Message which forces clients to parse text; change the
ValidationError type to use discrete fields (e.g., Property string, InvalidValue
interface{}, Expected interface{} or Required bool/Default interface{}, and
Reason string) so the API returns machine-readable JSON; update all
marshaling/unmarshaling usage and any code that constructs ValidationError
(search for ValidationError, NewValidationError, or places that set
ValidationError.Message) to populate the new fields and adjust consumer code to
read those fields instead of parsing Message.
pkg/crc/api/api_http_test.go (1)

514-539: LGTM — consider also asserting Content-Type: application/json in the response.

The non-deterministic map iteration handling is well-designed. The one gap is that writeMultiError explicitly sets Content-Type: application/json, but the test doesn't verify it. Since this is part of the new API contract, adding an assertion here (and in the table case at line 306) would lock it in:

require.Equal(t, "application/json", resp.Header.Get("Content-Type"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/crc/api/api_http_test.go` around lines 514 - 539, The test
TestSetConfigMultipleErrors should also assert the response Content-Type to lock
in the API contract set by writeMultiError; after you send the request and have
resp (e.g., after resp.StatusCode or after reading resp.Body) add a check like
require.Equal(t, "application/json", resp.Header.Get("Content-Type")). Do the
same assertion in the table-driven test case that exercises writeMultiError (the
earlier table case that validates multi-error responses) so both paths
explicitly verify the Content-Type header.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/crc/api/api_http_test.go`:
- Around line 306-309: The test is brittle because it asserts a raw JSON string
with HTML-escaped characters; change the assertion to be encoder-agnostic by
decoding the response body into []client.ValidationError and comparing the
resulting slice to the expected slice (same approach used in
TestSetConfigMultipleErrors). Locate the failing case that uses
post("config").withBody(`{"properties":{"cpus":0}}`) and the expected
httpError(500).withBody(...), read the response body produced in the test,
json.Unmarshal it into []client.ValidationError, and assert equality against the
expected []client.ValidationError (rather than matching the escaped string);
this will make the test resilient to changes in writeMultiError or
enc.SetEscapeHTML.

In `@pkg/crc/api/helpers.go`:
- Around line 135-147: The function writeMultiError builds validationErrors from
multiErr.Errors and currently calls e.Error() without checking for nil, which
can panic; also it sets http.StatusInternalServerError but these are client
validation issues and should use a 4xx status. Fix by guarding nil elements in
multiErr.Errors inside writeMultiError (skip or provide a safe message when e ==
nil before calling e.Error()) when constructing client.ValidationError entries,
and change the response status from http.StatusInternalServerError to an
appropriate 4xx (choose http.StatusBadRequest or http.StatusUnprocessableEntity)
while noting this is a behavioral change; ensure the Content-Type header and
JSON encoding of validationErrors (client.ValidationError) remain unchanged and
retain the logging path on Encode error.

---

Nitpick comments:
In `@pkg/crc/api/api_http_test.go`:
- Around line 514-539: The test TestSetConfigMultipleErrors should also assert
the response Content-Type to lock in the API contract set by writeMultiError;
after you send the request and have resp (e.g., after resp.StatusCode or after
reading resp.Body) add a check like require.Equal(t, "application/json",
resp.Header.Get("Content-Type")). Do the same assertion in the table-driven test
case that exercises writeMultiError (the earlier table case that validates
multi-error responses) so both paths explicitly verify the Content-Type header.

In `@pkg/crc/api/client/types.go`:
- Around line 70-73: The ValidationError struct currently only has Message which
forces clients to parse text; change the ValidationError type to use discrete
fields (e.g., Property string, InvalidValue interface{}, Expected interface{} or
Required bool/Default interface{}, and Reason string) so the API returns
machine-readable JSON; update all marshaling/unmarshaling usage and any code
that constructs ValidationError (search for ValidationError, NewValidationError,
or places that set ValidationError.Message) to populate the new fields and
adjust consumer code to read those fields instead of parsing Message.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd8b3b and 15a920f.

📒 Files selected for processing (4)
  • pkg/crc/api/api_http_test.go
  • pkg/crc/api/client/client.go
  • pkg/crc/api/client/types.go
  • pkg/crc/api/helpers.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/crc/api/client/client.go

Comment thread pkg/crc/api/api_http_test.go
Comment thread pkg/crc/api/helpers.go
@praveenkumar
Copy link
Copy Markdown
Member

@praveenkumar I tested this change against the crc-extension source to assess Podman Desktop compatibility. Here are the findings:

What changes: The POST /api/config endpoint now returns a JSON array [{"message":"..."}] with Content-Type: application/json instead of plain text when config validation fails.

How crc-extension handles it: In daemon-commander.ts:108, configSet does throw new Error(result.body) on non-200 responses. This means the raw JSON string would become the error message shown to users (e.g., in preferences.ts via showErrorMessage).

Impact: Not a crash — but users would see [{"message":"Value '0' for..."}] instead of the clean text message. Cosmetic degradation.

Test evidence:

$ curl -X POST --unix-socket ~/.crc/crc-http.sock http://localhost/api/config \
  -d '{"properties":{"cpus":0,"memory":1}}'

[{"message":"Value '0' for configuration property 'cpus' is invalid, reason: requires CPUs >= 4"},
 {"message":"Value '1' for configuration property 'memory' is invalid, reason: requires memory in MiB >= 10752"}]

Options to address:

1. Update crc-extension to parse JSON error bodies (check `Content-Type` header or try `JSON.parse`)

2. Add a human-readable `Error()` string alongside the structured JSON (e.g., wrap in `{"error":"...","details":[...]}`)

3. Only use JSON array format when client sends `Accept: application/json`, fall back to plain text otherwise

Let me know which approach you'd prefer and I can implement it.

@evidolob can you take a look to this one and suggest what would be better for extension side?

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 15a920f to 79819fc Compare February 25, 2026 14:41
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

🧹 Nitpick comments (3)
pkg/crc/api/client/types.go (1)

70-73: Consider extending ValidationError with stable machine-readable fields.

message alone is human-friendly but brittle for client logic. Optional fields like property, value, and reason would make integrations more robust without breaking existing consumers of message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/crc/api/client/types.go` around lines 70 - 73, The ValidationError struct
only has a human-friendly Message which is brittle for client logic; update the
ValidationError type to include stable, optional machine-readable fields (e.g.
Property string `json:"property,omitempty"`, Value interface{}
`json:"value,omitempty"`, Reason string `json:"reason,omitempty"`) while keeping
the existing Message field and JSON tag to preserve backward compatibility; add
omitempty to new fields so existing consumers are unaffected and update any code
that constructs ValidationError (e.g., places that instantiate ValidationError)
to populate these fields where available.
pkg/crc/api/helpers.go (1)

145-147: Consider a migration-friendly error shape for legacy clients.

Always returning a JSON array can surface raw JSON strings in older clients. A transitional envelope (for example, {"error":"...","details":[...]}) or Accept-based fallback would preserve readability while keeping structured details.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/crc/api/helpers.go` around lines 145 - 147, The handler currently encodes
validationErrors directly as a JSON array; change it to write a
migration-friendly envelope object (e.g., {"error":"validation
failed","details": validationErrors}) by creating an envelope map or struct and
encoding that instead of validationErrors; additionally, inspect the request
Accept header and, if the client prefers text/plain or lacks JSON support, fall
back to a simple human-readable error string (or the "error" field) so legacy
clients see a readable message while newer clients get structured details;
update the write logic where validationErrors is encoded to use the new envelope
and Accept-based fallback.
pkg/crc/api/api_http_test.go (1)

523-530: Optional: assert Content-Type to lock the wire contract.

Since this PR formalizes JSON validation errors, adding a header assertion here would prevent regressions in response media type.

🧪 Suggested assertion
 	require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
+	require.Contains(t, resp.Header.Get("Content-Type"), "application/json")
 
 	body, err := io.ReadAll(resp.Body)
 	require.NoError(t, err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/crc/api/api_http_test.go` around lines 523 - 530, Add an assertion that
the response Content-Type is the expected JSON media type to lock the wire
contract: check resp.Header.Get("Content-Type") (using the exact expected value
used elsewhere in tests, e.g., "application/json" or "application/json;
charset=utf-8") before reading the body in the test (the block that currently
reads resp and unmarshals into validationErrors), so the test asserts the
response media type alongside the status and validation error payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/crc/api/client/client.go`:
- Around line 261-262: The error paths currently ignore errors from io.ReadAll
and return an HTTPError with an empty Body; update the error-path reads to
mirror the success path: call body, err := io.ReadAll(res.Body) and if err !=
nil return nil, fmt.Errorf("reading response body: %w", err) (or propagate the
read error) instead of discarding it, then include the body in the returned
&HTTPError{... Body: string(body)}; apply this change in the request-handling
function where HTTPError is constructed so all branches consistently handle
io.ReadAll errors.

---

Nitpick comments:
In `@pkg/crc/api/api_http_test.go`:
- Around line 523-530: Add an assertion that the response Content-Type is the
expected JSON media type to lock the wire contract: check
resp.Header.Get("Content-Type") (using the exact expected value used elsewhere
in tests, e.g., "application/json" or "application/json; charset=utf-8") before
reading the body in the test (the block that currently reads resp and unmarshals
into validationErrors), so the test asserts the response media type alongside
the status and validation error payload.

In `@pkg/crc/api/client/types.go`:
- Around line 70-73: The ValidationError struct only has a human-friendly
Message which is brittle for client logic; update the ValidationError type to
include stable, optional machine-readable fields (e.g. Property string
`json:"property,omitempty"`, Value interface{} `json:"value,omitempty"`, Reason
string `json:"reason,omitempty"`) while keeping the existing Message field and
JSON tag to preserve backward compatibility; add omitempty to new fields so
existing consumers are unaffected and update any code that constructs
ValidationError (e.g., places that instantiate ValidationError) to populate
these fields where available.

In `@pkg/crc/api/helpers.go`:
- Around line 145-147: The handler currently encodes validationErrors directly
as a JSON array; change it to write a migration-friendly envelope object (e.g.,
{"error":"validation failed","details": validationErrors}) by creating an
envelope map or struct and encoding that instead of validationErrors;
additionally, inspect the request Accept header and, if the client prefers
text/plain or lacks JSON support, fall back to a simple human-readable error
string (or the "error" field) so legacy clients see a readable message while
newer clients get structured details; update the write logic where
validationErrors is encoded to use the new envelope and Accept-based fallback.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15a920f and 79819fc.

📒 Files selected for processing (4)
  • pkg/crc/api/api_http_test.go
  • pkg/crc/api/client/client.go
  • pkg/crc/api/client/types.go
  • pkg/crc/api/helpers.go

Comment thread pkg/crc/api/client/client.go Outdated
@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 79819fc to 8c33284 Compare February 25, 2026 15:21
@evidolob
Copy link
Copy Markdown
Contributor

I think it is better to update the crc-extension to handle/parse json error messages, it would improve user averseness if something going wrong. But I not sure who is responsible to made thous changes in extension.

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 8c33284 to 9037c0b Compare February 26, 2026 20:20
@praveenkumar
Copy link
Copy Markdown
Member

I think it is better to update the crc-extension to handle/parse json error messages, it would improve user averseness if something going wrong. But I not sure who is responsible to made thous changes in extension.

extension still part of crc org so if you want to update it once this PR merge, that should be doable.

@sebrandon1 another thing which I want to point out is we are in progress to change some of the component around how crc works today.

  • We want to remove the complete api layer and extension should just use cli to interact.
  • For the VM management, considering macadam ..etc.

So if that is not that urgent or breaking something , I would say let's not spend time on that and extension side and in future this will not even present.

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 9037c0b to 8189bd8 Compare February 27, 2026 14:43
@sebrandon1
Copy link
Copy Markdown
Contributor Author

Okay no worries! Good to know.

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch 2 times, most recently from d05a5b7 to 90f0947 Compare March 11, 2026 15:28
@praveenkumar
Copy link
Copy Markdown
Member

/hold

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 90f0947 to 5d2bb04 Compare March 16, 2026 18:31
@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch 2 times, most recently from fc2e4d9 to de776f9 Compare March 23, 2026 18:52
@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch 3 times, most recently from 13aa381 to 3ac6bfd Compare April 8, 2026 13:30
@sebrandon1
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sebrandon1 sebrandon1 closed this Apr 8, 2026
@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 3ac6bfd to eabb0f6 Compare April 8, 2026 15:05
@sebrandon1 sebrandon1 reopened this Apr 8, 2026
@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 8, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign anjannath for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

1 similar comment
@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 8, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign anjannath for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from d4a832c to 84ce09c Compare April 8, 2026 18:37
When setting config via the API with invalid values, detect MultiError
at the HTTP response layer and return a JSON array of structured
ValidationError objects with HTTP 422 and Content-Type: application/json.
Preserve error response body in the API client via HTTPError.

Resolves crc-org#2973
@sebrandon1 sebrandon1 force-pushed the structured-json-config-errors branch from 84ce09c to 79fcb38 Compare April 13, 2026 16:15
@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 13, 2026

@sebrandon1: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-crc 79fcb38 link true /test e2e-crc

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Improve error messages for API configuration save

3 participants