OpenAPITools / OpenAPITools/openapi-generator

[BUG] [rust-client] `oneOf` is mapped into a struct instead of enum

Open
#9,497 6 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.