OpenAPITools / OpenAPITools/openapi-generator
[BUG][RUST] `#[serde(tag)]` conflicts with discriminator property in nested objects
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
When generating Rust code from an OpenAPI spec that uses oneOf with a discriminator, the generator produces #[serde(tag = "type")] for the enum. However, when the enum variants are newtype wrappers around objects that also contain the discriminator field, serde fails to deserialize because it cannot find the type field in the expected location.
The rust models generated look like this:
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PersonResource {
#[serde(rename = "type")]
pub r#type: Type,
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "name", skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct OrganizationResource {
#[serde(rename = "type")]
pub r#type: Type,
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "companyName", skip_serializing_if = "Option::is_none")]
pub company_name: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ResourceUnion {
#[serde(rename="person")]
Person(models::PersonResource),
#[serde(rename="organization")]
Organization(models::OrganizationResource),
}
When deserializing JSON like this:
{
"type": "person",
"id": "123",
"name": "John Doe"
}
Serde fails with: Error("missing field 'type'", ...)
This happens because:
#[serde(tag = "type")]tells serde that the enum uses an internally-tagged representation- For newtype variants with
tag, serde expects the tag field to be separate from the variant's data - But the inner
PersonResourcestruct also expects atypefield - Serde removes the discriminator field when deserializing the enum, so
PersonResourcecan't find it
openapi-generator version
7.14.0 I believe it's not a regression.
OpenAPI declaration file content or url
https://gist.github.com/ThomasVille/06ebf97ed17966c6d0cb2565a89c5aa2
Generation Details
java -DloggerPath=conf/log4j.properties generate -i "spec.json" \
-g rust \
-o ${TMP_PATH} \
--additional-properties=packageName=my_client \
--additional-properties=publishRustRegistry=false \
--additional-properties=library=reqwest \
--additional-properties=supportMiddleware=true \
--additional-properties=avoidBoxedModels=true \
--additional-properties=preferUnsignedInt=true
Steps to reproduce
- Generate the SDK using the spec and the command above.
- In the
src/lib.rsfile add this test:
#[test]
fn deserialize_resource_union() {
let json = r#"{"type": "person", "id": "123", "name": "John Doe"}"#;
let result: models::ResourceUnion = serde_json::from_str(json).unwrap();
}
- Run the test with
cargo test. - The test fails with:
running 1 test
test deserialize_resource_union ... FAILED
failures:
---- deserialize_resource_union stdout ----
thread 'deserialize_resource_union' panicked at src/lib.rs:16:68:
called `Result::unwrap()` on an `Err` value: Error("missing field `type`", line: 0, column: 0)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
deserialize_resource_union
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Related issues/PRs
- These issues seem similar:
- This PR seems to fix the issue for Axum but I don't understand the fix: https://github.com/OpenAPITools/openapi-generator/pull/20336.
This pattern is very common in JSON:API specifications where all resource objects have a type field, so I believe this is a widespread issue that may already have a solution.
Suggest a fix
I don't see any clean solution for now.
Here are a few leads:
- Using
#[serde(untagged)]doesn't work when all variants have the same structure, which is typical when conforming to the JSON:API spec and is possible even in other situations as described in the example above. - Automatically detecting this situation (there's a discriminator and all the newtype variants have the discriminator field) and removing the field from the newtypes when generating the model: likely a breaking change.
- Making the newtype variants not have the discriminator field in the OpenAPI spec, but this doesn't match with the JSON:API spec when a response has an
includedfield where the items of the field should be valid resource objects (thus have thetypefield):
{
"data": [
{
"type": "node",
"id": "019a5ee2-862f-77d1-801c-1a08f9172167",
"attributes": {},
"relationships": {}
}
],
"included": [
{ // This is expected to be a valid resource object with `type`, `id`, ...
"type": "mesh",
"id": "019a5ee2-862a-7011-88f1-4b1c590a00d2",
"attributes": {},
"relationships": {}
}
]
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the issue from the linked OpenAPI declaration using the provided Java generation command, then add the deserialize_resource_union test to src/lib.rs and run cargo test. Inspect the generated ResourceUnion, PersonResource, and OrganizationResource models and trace the Rust generator templates that produce their serde attributes. Done means the generated model correctly deserializes the sample JSON without regressing discriminator handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- openapi, rust
- Domain
- api, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100