Improve error rendering and add required-args guardrails#2
Merged
Merged
Improve error rendering and add required-args guardrails#2
Conversation
The CLI previously rendered 400 responses as 'API returned HTTP 400.' with the raw JSON dump hidden behind --verbose. The server already returns structured validation errors; this surfaces them. errors::render_api_error now: - Walks JSON bodies for error/errors/message/messages keys, accepting strings, arrays of strings, arrays of objects, and nested objects. - Treats 400/422 as 'invalid request.' with one bullet per extracted message. - For 'X must be one of: …' bullets, runs Levenshtein against argv and appends a did-you-mean when the best match is within distance 3 and shares a 3-char prefix. - Truncates long enum lists to 5 + '(N more)'. - Maps known field names (network, chain) to follow-up commands. - Falls back to printing the raw body when JSON parsing fails. - Verbose still dumps the full body. Adds render_with_argv so the integration-test harness can use the simulated argv instead of the test binary's process argv. 17 new unit tests + 2 wiremock cases in tests/webhooks.rs.
Closes the 'empty-body 400' gap audited across the create/update surface: where the user supplies zero (or trivially insufficient) flags, the server today returns a 400 with no useful body — the new Stage A renderer can't help. These commands now fail fast client-side with a CliError::Arg naming the missing flags. - endpoint create: require at least --chain or --network - endpoint update: require --label - endpoint security set-options: require at least one toggle - team set-endpoints: require at least one endpoint id webhook create's required fields are already enforced by clap. Four integration tests assert exit code 1 and zero HTTP traffic.
The server requires both fields, not 'at least one'. Tighten the pre-flight to name whichever flag (or both) the user omitted instead of letting --chain-only through to a 400.
markwu35
approved these changes
Jun 5, 2026
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Today, bad input to
qnproduces opaque output. A 400 from the API gets rendered asError: API returned HTTP 400.with the structured validation body hidden behind--verbose; anendpoint createwith zero flags returns a 400 with an empty body, leaving the user with no signal at all.This PR is two stages:
Stage A — universal error-body parsing in
src/errors.rs. Walks JSON 400 bodies for any oferror,errors,message,messages(strings, arrays, nested objects — covers NestJS, JSON:API, admin-wrapper, and ad-hoc shapes). Renders one bullet per extracted message under a cleanError: invalid request.headline. When a bullet matchesX must be one of …, runs Levenshtein against the user's argv and appendsdid you mean '…'?if the best match is within distance 3 and shares a 3-char prefix. Truncates long enum lists to 5 +(N more). Falls back to the raw body when JSON parsing fails, and--verbosestill dumps everything.Stage B — required-args pre-flight on four commands. Where the server returns an empty 400 because the user gave nothing to act on, fail fast client-side with a clear message instead. Covers
endpoint create,endpoint update,endpoint security set-options, andteam set-endpoints.Before / after
Test plan
cargo test— 172 passing, including 17 new unit tests inerrorsand 6 new integration tests (tests/webhooks.rs,tests/endpoint.rs,tests/admin_extra.rs)cargo clippy --all-targets -- -D warningscleancargo fmt --checkclean