Skip to content

[go] read rfc7807 title and detail through their pointers - #24853

Open
midwell wants to merge 1 commit into
OpenAPITools:masterfrom
midwell:fix/go-client-rfc7807-pointer-fields
Open

[go] read rfc7807 title and detail through their pointers#24853
midwell wants to merge 1 commit into
OpenAPITools:masterfrom
midwell:fix/go-client-rfc7807-pointer-fields

Conversation

@midwell

@midwell midwell commented Sep 3, 2026

Copy link
Copy Markdown

Fixes #20553.

The defect

formatErrorMessage formats the field's Interface() with %s. Optional rfc7807 members are
generated as *string, and %s on a pointer renders the address — so the text the function exists
to surface is replaced by it in the error a caller prints. Reproducing the issue's example against
a client generated from master:

400 Bad Request %!s(*string=0x14000114510) (%!s(*string=0x14000114520))

Both states were wrong, not only the populated one. Every rfc7807 member is optional, so a model
carrying just a status has both nil, and the old code printed the nils:

400 Bad Request %!s(*string=<nil>) (%!s(*string=<nil>))

The test it used, field != (reflect.Value{}), also cannot distinguish a member that is absent
from one that is present and unset. Of the three states a member can be in, only the first was
handled.

The fix

rfc7807Field follows a single level of pointer indirection and returns the empty string for all
three "nothing to say" cases: no such field, a nil pointer, an empty string. The message is then
assembled from the members that have something in them, so an unset detail no longer contributes an
empty ().

Same client, after:

model before after
title and detail set 400 Bad Request %!s(*string=0x…) (%!s(*string=0x…)) 400 Bad Request Bad Request (id must be positive)
both unset 400 Bad Request %!s(*string=<nil>) (%!s(*string=<nil>)) 400 Bad Request
title set, detail unset 400 Bad Request %!s(*string=0x…) (%!s(*string=<nil>)) 400 Bad Request Bad Request
model passed by value panics, reflect: call of reflect.Value.Elem on struct Value 400 Bad Request Bad Request
untyped nil panics, reflect: call of reflect.Value.Elem on zero Value 400 Bad Request

Non-pointer string members keep their existing output. The one deliberate behaviour change is
that a member with nothing in it is omitted rather than rendered.

The last two rows are a side effect rather than the point: the unconditional Elem() panicked for
a non-pointer or nil v. Generated code always passes &v, so that is hygiene rather than a
reachable defect, and it costs nothing because reading through a pointer member needs the Kind
checks anyway. A typed nil pointer was already safe and still is. This is the same area as #15154,
which added the Kind() == reflect.Struct guard for #15147.

Returning only the status was considered and rejected: it removes the symptom by discarding the
detail, which is the information the function exists to carry.

Samples

Updated for the 12 Go clients that carry this function. I do not have a JVM on this machine, so I
could not run ./mvnw clean package — instead the sample edits were proved equal to regeneration
output
rather than assumed:

  • generated the same spec twice with openapi-generator generate -g go -t <templates>, once with
    master's go templates and once with this branch's, per the -t note in CONTRIBUTING;
  • exactly one file differs between the two generations, client.go, and its delta is exactly this
    function change;
  • the changed region of all 13 committed files — the template and all 12 samples — is
    byte-identical to that generated output.

The function contains no mustache tags, which is why the substitution is identical in the template
and in every sample. Nothing else in the samples is touched, and no go.mod/go.sum is modified.

Static checks on the generated output, each compared against master rather than read on its own:

  • go vet on a client generated from the fixed template: clean.
  • staticcheck v0.8.1 on the same: 8 findings before the change and the same 8 after, an
    identical set, none of them in the changed functions.
  • gofmt wants no change in the new code. It does list these client.go files, but it does so on
    master too — the generated output has pre-existing spacing quirks such as
    strings.NewReplacer( "%5B", ... ), none of them in this diff.
  • 11 of the 12 changed samples build. The twelfth,
    samples/openapi3/client/petstore/go/go-petstore-aws-signature, fails identically before and
    after
    this change (missing go.sum entry for github.com/aws/aws-sdk-go-v2), so its go.sum is
    incomplete on master; unrelated to this diff and left alone.

PR checklist

Fixes OpenAPITools#20553.

formatErrorMessage formatted the field's Interface() with %s. Optional rfc7807
members are generated as *string, and %s on a pointer renders the address, so
the text the function exists to surface is replaced by it in the error a caller
prints:

    400 Bad Request %!s(*string=0x40001de0d0) (%!s(*string=0x40001de0e0))

Both states were wrong, not only the populated one. Every member is optional, so
a model carrying just a status has both nil and the old code printed the nils:

    400 Bad Request %!s(*string=<nil>) (%!s(*string=<nil>))

The test it used, field != (reflect.Value{}), also cannot distinguish a member
that is absent from one that is present and unset, so of the three states a
member can be in only the first was handled.

rfc7807Field follows a single level of pointer indirection and returns the empty
string for all three "nothing to say" cases: no such field, a nil pointer, or an
empty string. The message is then built from the members that have something in
them, so an unset detail no longer contributes an empty "()".

Non-pointer string members keep their existing output. The one deliberate change
is that a member with nothing in it is omitted rather than rendered.

It also removes a panic on the unconditional Elem(): an untyped nil ("on zero
Value") and a model passed by value ("on struct Value"). Generated code always
passes &v, so that is hygiene rather than a reachable defect, and it costs
nothing because reading through a pointer member needs the Kind checks anyway.
A typed nil pointer was already safe and still is.

Samples were updated for the 12 Go clients that carry this function.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 13 files

Re-trigger cubic

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.

[BUG][Go] client returns pointer address instead of the error message when the response has optional title/detail field

1 participant