Skip to content

fix: surface 422 validation error messages in SendDraft and UpdateDraft - #133

Merged
jeremy merged 4 commits into
basecamp:mainfrom
AruneshDwivedi:fix-422-body
Sep 10, 2026
Merged

fix: surface 422 validation error messages in SendDraft and UpdateDraft#133
jeremy merged 4 commits into
basecamp:mainfrom
AruneshDwivedi:fix-422-body

Conversation

@AruneshDwivedi

@AruneshDwivedi AruneshDwivedi commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

The generated client parses 422 responses into JSON422, but the service wrappers (UpdateDraft and SendDraft) discarded this and fell back to CheckResponse which returns a generic validation error string. Parse JSON422 and return specific messages from the response body instead.


Summary by cubic

UpdateDraft and SendDraft now return the specific validation error messages from HEY API 422 responses instead of a generic "validation error" string. The error keeps the HTTP status and X-Request-Id, and falls back to the generic message when the response body carries no reasons.

  • Adds conformance tests for both operations answering 422 with a reasons list.

Written for commit cdae58e. Summary will update on new commits.

Review in cubic

Copilot AI balanced review requested due to automatic review settings August 31, 2026 01:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Surfaces HEY API validation details from 422 responses in draft operations.

Changes:

  • Parses JSON422 errors in UpdateDraft and SendDraft.
  • Returns validation messages to callers.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Suppressed comments (2)

go/pkg/hey/messages.go:234

  • This branch bypasses CheckResponse but only sets the code and message, so parsed 422 errors now report HTTPStatus == 0 and drop the response's X-Request-Id. That breaks the structured validation-error contract asserted in conformance/tests/error-mapping.json:64 and the request-ID handling in helpers.go:24-41. Construct the standard validation error and preserve the request ID.
			return &Error{
				Code:       CodeValidation,
				Message:    strings.Join(resp.JSON422.Errors, ", "),
			}

go/pkg/hey/messages.go:277

  • As in UpdateDraft, this manually constructed error loses the actual 422 status and request ID because CheckResponse is skipped. Callers inspecting Error.HTTPStatus receive zero even though the server returned 422; use the shared validation constructor and copy the response request ID.
			return &Error{
				Code:       CodeValidation,
				Message:    strings.Join(resp.JSON422.Errors, ", "),
			}

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread go/pkg/hey/messages.go Outdated
AruneshDwivedi and others added 4 commits September 9, 2026 16:35
When HEY returns HTTP 422 with validation errors in the response body,
the SDK currently discards them and returns a generic 'validation error'.
Parse the JSON422 body and return the specific error messages so callers
can show the user what went wrong.
Same fix as UpdateDraft: parse JSON422 body and return specific validation
error messages instead of a generic 'validation error' string.
A parsed 422 body is the message, but the error it became dropped the
HTTP status and the X-Request-Id that CheckResponse puts on every other
status, so a caller inspecting HTTPStatus saw zero for a request the
server refused. draftWriteError builds the error through ErrValidation
and copies the request id, falling back to the body's message when it
carries no errors list, and the bodyless 422 still maps by status.

Tests cover UpdateDraft and SendDraft with a reasons list, and a 422
with no body at all.
The Go and Rust runners both dispatch UpdateDraft and SendDraft through
the HEY layer, so a case that answers one with a reasons list and asserts
the code, status, retryability and request id holds both SDKs to the same
contract. Against the wrapper that returned a bare error with the reasons
and nothing else, both cases fail.
@jeremy

jeremy commented Sep 10, 2026

Copy link
Copy Markdown
Member

Rebased onto main and pushed two commits on top.

4bdeb63 keeps the structured error contract on the parsed 422: the error built from the reasons list dropped the HTTP status and the X-Request-Id that CheckResponse puts on every other status, so a caller inspecting HTTPStatus saw zero for a request the server refused. draftWriteError builds it through ErrValidation (reasons joined with ; , as the rest of the SDK does) and copies the request id, falling back to the body's message when it carries no errors, and a bodyless 422 still maps by status. Unit tests cover UpdateDraft and SendDraft with a reasons list and a 422 with no body.

cdae58e adds the conformance case: both runners dispatch UpdateDraft and SendDraft through the HEY layer, so a 422 with a reasons list asserting code, status, retryability and request id holds Go and Rust to the same contract — against the wrapper as first pushed, both cases fail; with the fix they pass, and Rust already passed since its transport maps every 422 this way.

make check passes locally. The Test and Security runs are waiting on a maintainer to approve them for a first-time contributor, so CI is not green yet.

@jeremy
jeremy merged commit 9cb5d59 into basecamp:main Sep 10, 2026
15 checks passed
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.

3 participants