Surface key, message, and faults in ResponseError#62
Open
Conversation
The API's error envelope uses `key` (not `code`) at the top level and now includes a `faults` array for structured validation details. The old ResponseError struct looked for `json:"code"`, which the API has never sent, so ResponseError.Error() produced strings like "422: " with no context. Rename Code -> Key to match the wire format, add Faults, and rewrite Error() to surface whatever the server provided, falling back to the raw response body when no known fields are populated. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ed5c597 to
b0227dd
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Updates ResponseError handling in the invopop Go client to better surface server-provided error details (key/message/faults) and improves dependency versions in the module.
Changes:
- Renames
ResponseError.CodetoResponseError.Keyand updates JSON tags to match the API error envelope. - Captures structured validation details via
ResponseError.Faultsand composes richerError()strings (including a raw-body fallback). - Bumps several Go module dependencies in
go.mod/go.sum.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
invopop/errors.go |
Enhances response error decoding and string formatting; adds fault support and a fault description helper. |
go.mod |
Updates required dependency versions. |
go.sum |
Updates checksums to match the dependency bumps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+65
to
+67
| fs = append(fs, f.describe()) | ||
| } | ||
| parts = append(parts, strings.Join(fs, "; ")) |
Comment on lines
54
to
+74
| func (r *ResponseError) Error() string { | ||
| if r.Code != "" { | ||
| return fmt.Sprintf("%d: (%s) %s", r.response.StatusCode(), r.Code, r.Message) | ||
| parts := []string{fmt.Sprintf("%d", r.response.StatusCode())} | ||
| if r.Key != "" { | ||
| parts = append(parts, r.Key) | ||
| } | ||
| if r.Message != "" { | ||
| parts = append(parts, r.Message) | ||
| } | ||
| if len(r.Faults) > 0 { | ||
| fs := make([]string, 0, len(r.Faults)) | ||
| for _, f := range r.Faults { | ||
| fs = append(fs, f.describe()) | ||
| } | ||
| parts = append(parts, strings.Join(fs, "; ")) | ||
| } | ||
| if len(parts) == 1 { | ||
| if body := strings.TrimSpace(r.response.String()); body != "" { | ||
| parts = append(parts, body) | ||
| } | ||
| } | ||
| return strings.Join(parts, ": ") |
Comment on lines
+12
to
+18
| github.com/invopop/gobl v0.401.0 | ||
| github.com/labstack/echo-contrib v0.17.4 | ||
| github.com/labstack/echo/v4 v4.13.4 | ||
| github.com/labstack/echo/v4 v4.15.0 | ||
| github.com/magefile/mage v1.15.0 | ||
| github.com/nats-io/nats.go v1.43.0 | ||
| github.com/rs/zerolog v1.34.0 | ||
| github.com/stretchr/testify v1.10.0 | ||
| github.com/stretchr/testify v1.11.1 |
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
ResponseError.Code→Keyto match the API's actual error envelope (the server sendskey, notcode, at the top level — the old field never matched and was always empty).ResponseError.Faults []*Faultso structured validation details from the API (e.g.GOBL-TAX-IDENTITY-01on specific JSON paths) are captured instead of discarded.ResponseError.Error()to compose a string from whatever the server provided (status: key: message: fault1; fault2; ...), falling back to the raw response body when nothing matches.Motivation
On an invalid silo upsert, callers were seeing errors like:
…with no diagnostic detail, even though the API was returning a full validation payload:
{ "key": "validation", "faults": [ { "code": "GOBL-TAX-IDENTITY-01", "paths": ["$.data.doc.customer.tax_id.country"], "message": "tax id country code is always required" } ], "fields": {...} }The
Codefield onResponseErrorhad the JSON tagcode, but the API envelope (api/internal/interfaces/web/utils/errors.go) useskey. SoCodewas never populated, theif r.Code != ""branch inError()never triggered, and the output collapsed to"422: ".With this change, the same error now surfaces as:
Compatibility
StatusCode(),Response(),IsConflict,IsNotFound,IsForbidden,AsResponseError, anderrors.As/errors.Isbehavior are all unchanged — they never read the renamed field.ResponseError.Code, so the rename is safe.Fields *Dictis preserved; the API also auto-populatesfieldsfromfaultsfor backward compatibility.Faulttype is reused fromtransform_jobs.go(already a superset of the envelopeFaultshape).Test plan
go build ./...go test ./invopop/...🤖 Generated with Claude Code