OpenAPITools / OpenAPITools/openapi-generator
[BUG] [rust-client] `oneOf` is mapped into a struct instead of enum
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 (example)?
- 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
oneOf support for models is broken for rust-client. Serde will always fail when parsing responses that contain a model with oneOf
openapi-generator version
5.1.1 and also tested with master
OpenAPI declaration file content or url
https://raw.githubusercontent.com/ory/sdk/master/spec/kratos/v0.6.2-alpha.1.json
There are two problematic cases of oneOf in that spec
The first one is a case in which all possible values are json scalar values. For example:
"uiNodeInputAttributesValue": {
"oneOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" }
]
},
gets trasnformed into
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct UiNodeInputAttributesValue {
}
impl UiNodeInputAttributesValue {
pub fn new() -> UiNodeInputAttributesValue {
UiNodeInputAttributesValue {
}
}
}
but this is wrong because UiNodeInputAttributesValue is not a struct.
My hack for this particular case is to use:
pub type UiNodeInputAttributesValue = serde_json::Value;
However, this does not solve all cases, since oneOf can also be used with references to other schemas. Indeed, there is another problematic case in this spec:
"uiNodeAttributes": {
"oneOf": [
{ "$ref": "#/components/schemas/uiNodeInputAttributes" },
{ "$ref": "#/components/schemas/uiNodeTextAttributes" },
{ "$ref": "#/components/schemas/uiNodeImageAttributes" },
{ "$ref": "#/components/schemas/uiNodeAnchorAttributes" }
],
"title": "Attributes represents a list of attributes (e.g. `href=\"foo\"` for links)."
},
Since this is a valid spec and all the models are present, the files for each type of uiNodeAttributes is created ok and accurately. However, the generated code for uiNodeAttributes is incorrect. The output result merges all properties like:
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct UiNodeAttributes {
/// Sets the input's disabled field to true or false.
#[serde(rename = "disabled")]
pub disabled: bool,
#[serde(rename = "label", skip_serializing_if = "Option::is_none")]
pub label: Option<Box<crate::models::UiText>>,
/// The input's element name.
#[serde(rename = "name")]
pub name: String,
/// The input's pattern.
#[serde(rename = "pattern", skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
/// Mark this input field as required.
#[serde(rename = "required", skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(rename = "type")]
pub _type: String,
#[serde(rename = "value", skip_serializing_if = "Option::is_none")]
pub value: Option<Box<crate::models::UiNodeInputAttributesValue>>,
#[serde(rename = "text")]
pub text: Box<crate::models::UiText>,
/// The image's source URL. format: uri
#[serde(rename = "src")]
pub src: String,
/// The link's href (destination) URL. format: uri
#[serde(rename = "href")]
pub href: String,
#[serde(rename = "title")]
pub title: Box<crate::models::UiText>,
}
impl UiNodeAttributes {
pub fn new(disabled: bool, name: String, _type: String, text: crate::models::UiText, src: String, href: String, title: crate::models::UiText) -> UiNodeAttributes {
UiNodeAttributes {
disabled,
label: None,
name,
pattern: None,
required: None,
_type,
value: None,
text: Box::new(text),
src,
href,
title: Box::new(title),
}
}
}
But this is extremely problematic and wrong, as that struct will never exist. UiNodeAttributes is assumed to have ALL properties from the polymorphic types. Since each of those types have different required properties, the UiNodeAttributes assumes that ALL required properties will always exist, which will never happen.
Generation Details
docker run --rm -v "${PWD}:/local" \
--user 1001:1001 \
openapitools/openapi-generator-cli generate \
-i https://raw.githubusercontent.com/ory/sdk/master/spec/kratos/v0.6.2-alpha.1.json \
-c /local/options.yaml \
-g rust \
-o /local/packages/kratos
Steps to reproduce
Run the command above
Related issues/PRs
https://github.com/OpenAPITools/openapi-generator/pull/2244 added support for multiple response types, but not for oneOf
Suggest a fix
Ideally, the generated code should generate Rust enums in the same way that polymorphism is treated with response errors
Another solution could be to continue merging all possible properties but make them all Option
The accepted answer in https://stackoverflow.com/questions/37561593/how-can-i-use-serde-with-a-json-array-with-different-objects-for-successes-and-e summarises the two possibilities
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 with options.yaml and the documented openapi-generator-cli command using -g rust and the linked OpenAPI declaration. Compare the generated UiNodeInputAttributesValue and UiNodeAttributes models with the referenced response-type work in PR #2244. Done means oneOf scalar and referenced schemas generate valid Rust representations that Serde can parse without merging incompatible required properties.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- openapi, rust
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100