[rust] fix: oneOf discriminator with an enum-typed tag

TE
4d ago
+1816-0

Fixes #22344
Fixes #22018

When the variants of a oneOf declare the discriminator property as an enum (inline or $ref), the union gets #[serde(tag = "...")] while each variant struct still has the field. Serializing writes the tag twice, and deserializing a valid payload fails with missing field, since serde consumes the tag before the variant sees it.

For those unions the template now implements Serialize/Deserialize instead of using serde(tag):

  • serialize: write the variant as is, adding the tag only if the variant left it unset
  • deserialize: match on the tag and pass the whole object, tag included, to the variant

This is the same approach rust-axum takes. Variant structs don't change, and plain-string tags and allOf unions keep their current output.

This is an alternative to #22489, which stalled.

Still unsupported, same as on master: discriminators without mapping, and several mapping values pointing to the same schema.

Tests: RustClientCodegenTest#testOneOfEnumDiscriminatorKeepsTagInContent (3.0 and 3.1 specs), and tests/enum_discriminator_test.rs in the reqwest composed-oneof sample.

cc @frol @farcaller @richardwhiuk @paladinzh @jacob-pro @dsteeley

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    Commit all changed files.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Fixes oneOf discriminated unions whose discriminator tag is an enum (inline or $ref) so the tag is written once and valid payloads deserialize instead of failing with a missing-field error.

  • For these unions, generates custom Serialize/Deserialize implementations instead of serde's tag attribute.
  • Serialization writes the variant payload as-is, then always overrides the tag to the mapped value; deserialization matches the tag and passes the whole payload, tag included, to the variant.
  • Plain string tags and allOf unions keep the existing serde(tag) output.
  • Still unsupported: discriminators without mapping, and several mapping values pointing to the same schema.