Swashbuckle.AspNetCore: There is a bug about Nullable enum with UseReferencedDefinitionsForEnums.

class Dto1 {
    public SomeEnum Some {get; set;}
}
class Dto2 {
    public SomeEnum? Some {get; set;}
}

It will generate redundant enums.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 26 (8 by maintainers)

Commits related to this issue

Most upvoted comments

@domaindrivendev I would like UseReferencedDefinitionsForEnums to work with nullable enums.

Maybe I’m missing something, but I thought the semantics of C# worked like this:

  1. Reference types (e.g., class instances) are nullable.
  2. Value types (e.g. integers and enums) are not nullable unless explicitly made so using the “?” qualifier.

So having to put a [Required] on an enum seems backward to the language.

class MyClass
{
    public MyEnum RequiredEnum  { get; set; }
    public MyEnum? OptionalEnum  { get; set; }
}

It seems to me that the above should generate two references to MyEnum, one required one not.

    "MyClass": {
      "required": [
        "requiredEnum"
      ],
      "type": "object",
      "properties": {
        "requiredEnum": {
          "$ref": "#/definitions/MyEnum",
        },
        "optionalEnum": {
          "$ref": "#/definitions/MyEnum",
        }
      }
    }

@TimothyByrd the JSON that you’re expecting is actually invalid Swagger/OpenAPI because a “reference” schema MUST NOT have any properties present other than the $ref property. This presents a bit of a dilemna. On the one hand you probably want enum schema’s to be referenced as opposed to being redefined inline with every usage. But, if they’re referenced then you can’t really apply something “contextual” like nullable to the definition schema because it can be shared across different contexts. In fact, there’s an issue in the Swagger/OpenAPI repo addressing this exact problem.

Swashbuckle gives you two options here which may or may not suit your needs. You can apply the suggested workaround from that issue with the UseAllOfToExtendReferenceSchemas() setting: This would generate the following JSON for your example:

      "MyClass": {
        "type": "object",
        "properties": {
          "requiredEnum": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MyEnum"
              }
            ]
          },
          "optionalEnum": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MyEnum"
              }
            ],
            "nullable": true
          }
        }
      }

Or, you can force all enum schema’s to be defined inline by setting UseInlineDefinitionsForEnums(). This would generate the following JSON for your example:

      "MyClass": {
        "type": "object",
        "properties": {
          "requiredEnum": {
            "enum": [
              "Foo",
              "Bar"
            ],
            "type": "string"
          },
          "optionalEnum": {
            "enum": [
              "Foo",
              "Bar"
            ],
            "type": "string",
            "nullable": true
          }
        }
      }

I’m still slogging through upgrading 4.0.1 => 5.1.0. So now I’m asking why optionalEnum isn’t marked as nullable like this?

"MyClass1": {
  "type": "object",
  "properties": {
    "requiredEnum": {
      "$ref": "#/components/schemas/MyEnum"
    },
    "optionalEnum": {
      "$ref": "#/components/schemas/MyEnum",
      "nullable": true
    }
  }
}

@domaindrivendev the issue I have is that OptionalEnum isn’t a ‘MyEnum’. Given this code:

[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum MyEnum
{
    v1,
    v2
};

public class MyClass1
{
    public MyEnum RequiredEnum { get; set; }
    public MyEnum? OptionalEnum { get; set; }
}

The swagger I get is for it is:

"MyClass1": {
  "required": [
    "requiredEnum"
  ],
  "type": "object",
  "properties": {
    "requiredEnum": {
      "$ref": "#/definitions/MyEnum"
    },
    "optionalEnum": {
      "enum": [
        "v1",
        "v2"
      ],
      "type": "string"
    }
  }
},
"MyEnum": {
  "enum": [
    "v1",
    "v2"
  ],
  "type": "string"
},

Since OptionalEnum is not a reference to “#/definitions/MyEnum”, when I use Nswag to generate code, they come out as two different types.

So parts of my interface use my “MyEnum” enum and parts use things with names like “MyClass1OptionalEnum”. And then consumers of my SDK have to do weird casts.