Skip to content

fix(client): correctly marshal text/plain requests#1975

Merged
jamietanna merged 5 commits into
mainfrom
iss/1914
Mar 12, 2026
Merged

fix(client): correctly marshal text/plain requests#1975
jamietanna merged 5 commits into
mainfrom
iss/1914

Conversation

@jamietanna

@jamietanna jamietanna commented May 10, 2025

Copy link
Copy Markdown
Member

As noted in #1914, there are cases where trying to interact with a
text/plain endpoint that requires input, for instance when receiving a
UUID, may not render correctly.

We should first check if the type is a Stringer, aka has a String()
method, and use that - otherwise use fmt.Sprintf("%v", ...) to
generate a string type.

Via 0, we can make sure that we wrap the generated type in an empty
interface, so we can perform the type assertion.

This also adds a test case to validate the functionality for:

  • a UUID, which has a String() method
  • a float32, which is a primitive datatype that needs to use
    fmt.Sprintf

Co-authored-by: claude-sonnet:3.7-thinking

Closes #1914.
Supersedes #1922.

As noted in #1914, there are cases where trying to interact with a
`text/plain` endpoint that requires input, for instance when receiving a
UUID, may not render correctly.

We should first check if the type is a `Stringer`, aka has a `String()`
method, and use that - otherwise use `fmt.Sprintf("%v", ...)` to
generate a string type.

Via [0], we can make sure that we wrap the generated type in an empty
`interface`, so we can perform the type assertion.

This also adds a test case to validate the functionality for:

- a UUID, which has a `String()` method
- a `float32`, which is a primitive datatype that needs to use
  `fmt.Sprintf`

Co-authored-by: claude-sonnet:3.7-thinking <github-copilot@jamietanna.co.uk>

Closes #1914.

[0]: https://www.jvt.me/posts/2025/05/10/go-type-assertion-concrete/
Comment thread pkg/codegen/templates/client.tmpl Outdated
@jamietanna jamietanna modified the milestones: v2.5.0, v2.6.0 Jul 15, 2025
@LarsStegman

Copy link
Copy Markdown

@jamietanna any chance of this getting merged soon?

mromaszewicz and others added 2 commits March 5, 2026 14:57
For text/plain request bodies, the generated client code now:
1. Checks if the body type implements fmt.Stringer and uses String()
2. Falls back to fmt.Sprintf("%v", body) for primitive types
3. Returns an error for complex types (objects, arrays, composed
   schemas), directing the user to implement a String() method

Adds Schema.IsPrimitive() to distinguish primitive OpenAPI types
(string, integer, number, boolean) from complex ones at codegen time.

Closes #1914.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mromaszewicz mromaszewicz marked this pull request as ready for review March 5, 2026 23:14
@mromaszewicz mromaszewicz requested a review from a team as a code owner March 5, 2026 23:14
@mromaszewicz

Copy link
Copy Markdown
Member

See this comment on a related PR:
#1922 (comment)

I've updated this PR, implementing that comment. I think this solves everyone's use case, and it errors out on impossible, undefined behavior.

@mromaszewicz

Copy link
Copy Markdown
Member

@greptileai

Co-authored-by: Gaiaz Iusipov <g.iusipov@gmail.com>
@greptile-apps

greptile-apps Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes incorrect text/plain request body serialization for types that are not directly convertible to string (e.g. openapi_types.UUID). The old template unconditionally used string(body), which produces the raw memory representation for non-string types like [16]byte. The fix introduces a Stringer-first check (interface{}(body).(fmt.Stringer)), with a fmt.Sprintf("%v", body) fallback for primitive OpenAPI types, and a compile-time-generated runtime error for complex non-Stringer types.

Key changes:

  • pkg/codegen/schema.go: New IsPrimitive() helper on Schema returns true for string, integer, number, and boolean OpenAPI types; used in the template to gate the fmt.Sprintf fallback.
  • pkg/codegen/templates/client.tmpl: Replaces strings.NewReader(string(body)) with the three-branch Stringer/primitive/error strategy.
  • New integration test for issue Cannot convert text/plain uuid in requestBody to string #1914 covering a UUID body (exercises the Stringer path) and a float32 body (exercises the primitive fmt.Sprintf path).
  • The same template fix is reflected in the regenerated strict-server/client/client.gen.go.

One architectural concern: IsPrimitive() evaluates the OpenAPI schema type, not the actual generated Go type. When x-go-type is used to substitute a complex Go struct for a schema declared as type: string, the generated code will silently call fmt.Sprintf("%v", body) and produce a struct-like string instead of a proper payload. The error return path is gated away by IsPrimitive() before it can trigger. Users relying on x-go-type overrides for text/plain endpoints will need to ensure their custom type implements fmt.Stringer.

Confidence Score: 3/5

  • Safe to merge for the common case; introduces a silent serialization hazard for x-go-type-overridden string schemas without Stringer.
  • The fix correctly addresses the reported bug and the tests validate the two primary code paths. However, IsPrimitive() checking the OpenAPI schema type rather than the Go type creates a gap: schemas with type: string + x-go-type pointing to a non-Stringer complex Go type will silently generate fmt.Sprintf("%v", body) output rather than returning an error or using a proper marshaler. This is a pre-existing gap in the x-go-type + text/plain combination, but the new code makes it harder to catch because the explicit error path is now gated away by IsPrimitive().
  • pkg/codegen/templates/client.tmpl — the interaction between IsPrimitive() and x-go-type overrides should be verified or documented.

Important Files Changed

Filename Overview
pkg/codegen/schema.go Adds IsPrimitive() helper that returns true for string/integer/number/boolean OpenAPI schema types. Implementation is clean and guards against nil OAPISchema.
pkg/codegen/templates/client.tmpl Replaces the naive string(body) conversion with a Stringer-first, then primitive-fallback strategy. Key concern: IsPrimitive() gates the fallback on the OpenAPI schema type, which diverges from the actual Go type when x-go-type extensions are in use, potentially silencing serialization errors.
internal/test/issues/issue-1914/client_test.go Adds two targeted tests covering UUID (Stringer path) and float32 (fmt.Sprintf path). Tests are clear and use appropriate assertions; float assertion is reliable on modern Go but lacks an explanatory comment.

Last reviewed commit: 6ebcea7

Comment thread pkg/codegen/templates/client.tmpl
@jamietanna jamietanna merged commit e705964 into main Mar 12, 2026
34 checks passed
@jamietanna jamietanna deleted the iss/1914 branch March 12, 2026 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot convert text/plain uuid in requestBody to string

4 participants