-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
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: [Rust-Axum][Breaking Change] Improve the
oneOfmodel generator #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": {}
}
]
}