Accept bare strings in errors and messages arrays - #4367
Merged
Merged
Conversation
The v4 API envelope documents `errors` and `messages` as arrays of
{code, message} objects, and ResponseInfo is typed accordingly. Some
services do not follow that contract and emit bare strings instead.
The custom hostname service declares `Messages []string` on every one
of its response wrappers. When an account is over its custom hostname
quota, a successful create returns:
{
"result": { "id": "...", "hostname": "...", "status": "pending" },
"success": true,
"errors": [],
"messages": ["You have exceeded your custom hostname quota. ..."]
}
Decoding that fails with:
json: cannot unmarshal string into Go struct field
CustomHostnameResponse.Response.messages of type cloudflare.ResponseInfo
so the caller gets a nil result and an error even though the hostname
was created and the API reported success.
Add an UnmarshalJSON to ResponseInfo that accepts either shape. A bare
string is kept as the message with a zero code, since none is supplied.
Because errors and messages share the type, this also covers bare-string
errors.
The underlying contract violation is server side and is being raised
with the owning team separately; this keeps the SDK from discarding a
valid result in the meantime.
changelog-check does an exact match on .changelog/<PR#>.txt, and changelog-build uses the filename to link the PR in CHANGELOG.md.
ssicard
approved these changes
Sep 25, 2026
4 of 9 tasks
vaishakdinesh
added a commit
that referenced
this pull request
Sep 25, 2026
Add CHANGELOG entry for #4367
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.
Description
ResponseInfois typed as{code, message}, matching the documented v4 envelope incommon.yaml, wheremessagesis an array of objects with a required integercodeand stringmessage.Some services do not honour that contract and emit bare strings. The custom hostname service declares
Messages []stringon every one of its response wrappers, so when an account is over its custom hostname quota a successful create returns:{ "result": { "id": "92f106a5-6c39-47da-8e2b-d90821675d7c", "hostname": "app.example.com", "status": "pending" }, "success": true, "errors": [], "messages": ["You have exceeded your custom hostname quota. Additional usage will be billed accordingly."] }CreateCustomHostnamethen fails with:The caller gets a nil result and an error, even though the hostname was created and the API reported
"success": true. There is no way to recover the ID from the SDK, so callers can silently orphan a hostname they believe failed.This adds an
UnmarshalJSONtoResponseInfoaccepting either shape. A bare string is kept as the message with a zero code, since none is supplied.errorsandmessagesshare the type, so bare-string errors are covered too.Not just the quota case
The same service emits two other bare-string messages, so this is not a one-off:
"Successfully deleted certificate with ID: %s", returned on every successful delete.Scope
This is a client-side tolerance layer, not a fix for the root cause. The server-side contract violation is being raised with the owning team separately. Until that lands, this stops the SDK discarding a valid result.
Has your change been tested?
Yes.
TestResponseInfoUnmarshalJSON— table test over object form, object withoutcode, bare string, empty string, and a value that is neither (must still error).TestResponseInfoStringMessagesPreserveResult— decodes the exact customer payload intoCustomHostnameResponseand asserts the result ID, hostname and message text all survive.Both fail before the change and pass after. Full suite is green (
go test ./...), andgofmtreports no new issues — the two files it flags (certificate_authorities.go,certificate_authorities_test.go) are already unformatted onv0and are untouched here.Note this repo decodes with
github.com/goccy/go-jsonrather than stdlib; the unmarshaler was verified against both.Types of changes
No exported field or signature changes. Only
UnmarshalJSONis added — noMarshalJSON, so outbound encoding is unchanged. Responses that already decoded continue to decode identically.One behavioural note: a bare-string message yields
Code: 0. Nothing in this repo branches onCode, but callers that do will see zero rather than a real code. That is unavoidable, as the API does not send one.Checklist: