Skip to content

Accept bare strings in errors and messages arrays - #4367

Merged
vaishakdinesh merged 2 commits into
v0from
fix/response-info-string-messages
Sep 25, 2026
Merged

vaishakdinesh merged 2 commits into
v0from
fix/response-info-string-messages

Conversation

@vaishakdinesh

@vaishakdinesh vaishakdinesh commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Description

ResponseInfo is typed as {code, message}, matching the documented v4 envelope in common.yaml, where messages is an array of objects with a required integer code and string message.

Some services do not honour that contract and emit bare strings. The custom hostname service declares Messages []string on 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."]
}

CreateCustomHostname then fails with:

error unmarshalling the JSON response: json: cannot unmarshal string into Go struct field
CustomHostnameResponse.Response.messages of type cloudflare.ResponseInfo

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 UnmarshalJSON to ResponseInfo accepting either shape. A bare string is kept as the message with a zero code, since none is supplied. errors and messages share 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:

  • CA failover — "Failover mode is enabled as the primary CA is having issues. All new SSL orders using the following validation methods are being shifted to the secondary CA: 'http', 'txt'". This fires during CA incidents, breaking hostname creation exactly when it matters most.
  • Certificate delete — "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 without code, bare string, empty string, and a value that is neither (must still error).
  • TestResponseInfoStringMessagesPreserveResult — decodes the exact customer payload into CustomHostnameResponse and asserts the result ID, hostname and message text all survive.

Both fail before the change and pass after. Full suite is green (go test ./...), and gofmt reports no new issues — the two files it flags (certificate_authorities.go, certificate_authorities_test.go) are already unformatted on v0 and are untouched here.

Note this repo decodes with github.com/goccy/go-json rather than stdlib; the unmarshaler was verified against both.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

No exported field or signature changes. Only UnmarshalJSON is added — no MarshalJSON, 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 on Code, but callers that do will see zero rather than a real code. That is unavoidable, as the API does not send one.

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • This change is using publicly documented in cloudflare/api-schemas and relies on stable APIs.

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.
@vaishakdinesh
vaishakdinesh requested a review from a team September 25, 2026 18:52
@vaishakdinesh vaishakdinesh self-assigned this Sep 25, 2026
@vaishakdinesh
vaishakdinesh merged commit e61f6fc into v0 Sep 25, 2026
3 checks passed
@vaishakdinesh
vaishakdinesh deleted the fix/response-info-string-messages branch September 25, 2026 20:25
@vaishakdinesh vaishakdinesh mentioned this pull request Sep 25, 2026
4 of 9 tasks
vaishakdinesh added a commit that referenced this pull request Sep 25, 2026
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.

2 participants