Skip to content

Support partially tagged enums - #1585

Open
fnimick wants to merge 7 commits into
juhaku:masterfrom
fnimick:support_partially_tagged_enums
Open

Support partially tagged enums#1585
fnimick wants to merge 7 commits into
juhaku:masterfrom
fnimick:support_partially_tagged_enums

Conversation

@fnimick

@fnimick fnimick commented Jul 30, 2026

Copy link
Copy Markdown

Heavily based on #1521 - merge conflicts fixed, with additional tests added for the internally tagged and adjacently tagged serde options.

In addition, this now supports untagged members on plain enums, which were not implemented in the initial PR.


Original PR Message

See here: https://serde.rs/variant-attrs.html#untagged

Irrespective of the enum representation, serialize and deserialize this variant as untagged, i.e. simply as the variant's data with no record of the variant name.

For a simple example:

enum SomeEnum {
    Tagged(u32)
    #[serde(untagged)]
    Untagged(u32)
}

This would serialize to an optionally tagged integer:

{"Tagged": 3}
3

With resulting JSON Schema:

{
    "oneOf": [
        {"type": "object", "properties": {"Tagged": {"type": "integer"} }, "required": ["Tagged"] },
        {"type": "integer"}
    ]
}

Somewhat related, but NOT implemented in this PR (as it seems like it will take some deeper refactoring of the "plain enum" handling):

enum SomeEnum {
    One,
    Two,
    #[serde(untagged)]
    Null
}

For this enum, serde would emit JSON values "One", "Two" and null, but utoipa still generates a JSON schema for an enum:

{
    "enum": ["One", "Two", "Null"],
    "type": "string"
}

fnimick added 4 commits July 30, 2026 16:57
Based on juhaku#1521 - merge conflicts resolved.

See here: https://serde.rs/variant-attrs.html#untagged

> Irrespective of the enum representation, serialize and deserialize this variant as untagged, i.e. simply as the variant's data with no record of the variant name.

For a simple example:

```rust
enum SomeEnum {
    Tagged(u32)
    #[serde(untagged)]
    Untagged(u32)
}
```

This would serialize to an optionally tagged integer:

```json
{"Tagged": 3}
3
```

With resulting JSON Schema:

```json
{
    "oneOf": [
        {"type": "object", "properties": {"Tagged": {"type": "integer"} }, "required": ["Tagged"] },
        {"type": "integer"}
    ]
}
```

---

Somewhat related, but __NOT__ implemented in this PR (as it seems like it will take some deeper refactoring of the "plain enum" handling):

```rust
enum SomeEnum {
    One,
    Two,
    #[serde(untagged)]
    Null
}
```

For this enum, serde would emit JSON values `"One"`, `"Two"` and `null`, but utoipa still generates a JSON schema for an enum:

```json
{
    "enum": ["One", "Two", "Null"],
    "type": "string"
}
```
Set EnumSchemaType to Mixed rather than Plain for partially untagged
unit variant enums; this ensures schema is oneOf [enum, null] rather
than a flat enum list.
@Drifter-J

Drifter-J commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Hi, thanks for contribution - https://github.com/fnimick/utoipa/blob/58eb1dbda4a04831e899eadd1be943e6d03296cb/utoipa-gen/src/lib.rs#L489 would you mind changing this?

Plus please add the changelog :)

Comment thread utoipa-gen/src/component/schema.rs Outdated
{
.map(|variant| {
Ok(matches!(variant.fields, Fields::Unit)
&& !serde::parse_value(&variant.attrs)?.untagged)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I read the whole related code more carefully so from what I understood https://github.com/fnimick/utoipa/blob/58eb1dbda4a04831e899eadd1be943e6d03296cb/utoipa-gen/src/component/schema/enums.rs#L63C13-L63C70 is executed the duplicated per-variant parse. Do you think we can eliminate this? If not, we can keep it as it is.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Remove duplicate serde::parse_value

Filter skipped enums before determining whether enum is all unit and
non untagged - simplifies output
@fnimick

fnimick commented Aug 10, 2026

Copy link
Copy Markdown
Author

@Drifter-J I realized a potential issue in the output. An enum with the format:

enum Foo {
    One,
    #[serde(untagged)]
    Two(String),
    #[serde(untagged)]
    Three(String),
}

generates an openapi schema of:

{
  "oneOf": [
    {
      "enum": [
        "One"
      ],
      "type": "string"
    },
    {
      "type": "string"
    }
  ]
}

Is this an issue, that the untagged string branch is duplicated?

A similar issue happens with partially untagged unit enums, where there are duplicate null members in the ouput oneOf if multiple untagged variants are present.

@Drifter-J

Copy link
Copy Markdown
Collaborator

@Drifter-J I realized a potential issue in the output. An enum with the format:

enum Foo {
    One,
    #[serde(untagged)]
    Two(String),
    #[serde(untagged)]
    Three(String),
}

generates an openapi schema of:

{
  "oneOf": [
    {
      "enum": [
        "One"
      ],
      "type": "string"
    },
    {
      "type": "string"
    }
  ]
}

Is this an issue, that the untagged string branch is duplicated?

A similar issue happens with partially untagged unit enums, where there are duplicate null members in the ouput oneOf if multiple untagged variants are present.

Yes, it doesn't look like a valid schema and it seems like Three is collapsed as well. I would suggest fixing duplication at codegen time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants