Skip to content

OpenApi Enums generating no schema #58230

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
JohnGalt1717 opened this issue Oct 3, 2024 · 11 comments
Closed
1 task done

OpenApi Enums generating no schema #58230

JohnGalt1717 opened this issue Oct 3, 2024 · 11 comments
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved

Comments

@JohnGalt1717
Copy link

JohnGalt1717 commented Oct 3, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

(References)

#58195
#57979

Using the following Model which correctly gets created in the OpenApi.json file:

public sealed record class NotificationDto : RefDto
{
    public DateTimeOffset CreatedOn { get; init; }
    public bool Viewed { get; init; }
    public required NotificationTypes NotificationType { get; init; }
    public required string Subject { get; init; }
    public string? Body { get; init; }
    public string? ImageUrl { get; init; }
    public Guid? ItemId { get; init; }
}

This is the definition it creates:


     "NotificationDto": {
        "required": [
          "notificationType",
          "subject",
          "id"
        ],
        "type": "object",
        "properties": {
          "createdOn": {
            "type": "string",
            "format": "date-time"
          },
          "viewed": {
            "type": "boolean"
          },
          "notificationType": {
            "$ref": "#/components/schemas/NotificationTypes"
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string",
            "nullable": true
          },
          "imageUrl": {
            "type": "string",
            "nullable": true
          },
          "itemId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },

This creates a definition for the enum but the definition for the enum itself is empty:

      "NotificationTypes": {
        "type": "integer"
      },

Here's the NotificationTypes enum:

public enum NotificationTypes
{
    General = 0
}

Expected Behavior

This should be the base OpenApi 2 version that just has the text representation AND it should have OneOf syntax like OpenApi 3 has with Type, Enum value and Title (name)

Ideally it should also inject x-ms-enum if you want on enums and give key/values since it's so widely supported by OpenAPI client generators.

Steps To Reproduce

Create an endpoint that returns the above object.
Generate the openapi.json file.

You'll see the bogus output.

Exceptions (if any)

No response

.NET Version

9.0.100-rc.1.24452.12

Anything else?

This also happens with the latest nighly 9.0.0-rtm.24477.5

It did work at one point in the beta process but is broken now. I don't know where it broke.

Also, I have the following that I was using to put in the 3.1 and x-ms-enum functionality:

internal class FixEnumsSchemaTransformer : IOpenApiSchemaTransformer
{
    public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context,
        CancellationToken cancellationToken)
    {
        Console.WriteLine(context.JsonTypeInfo.Type.Name);

        if (!schema.Enum.Any())
            return Task.CompletedTask;

        if (schema.Type != "integer")
            return Task.CompletedTask;

        var enumType = schema.Enum.First().GetType();

        // Add x-ms-enum extension
        var enumValues = new OpenApiArray();
        enumValues.AddRange(Enum.GetNames(enumType)
            .Select(name => new OpenApiObject
            {
                ["name"] = new OpenApiString(name),
                ["value"] = new OpenApiInteger((int)Enum.Parse(enumType, name)),
                ["description"] = new OpenApiString(GetEnumDescription(enumType, name))
            }));

        schema.Extensions["x-ms-enum"] = new OpenApiObject
        {
            ["name"] = new OpenApiString(enumType.Name),
            ["modelAsString"] = new OpenApiBoolean(false),
            ["values"] = enumValues
        };

        // Add enum schemas to OneOf
        foreach (var name in Enum.GetNames(enumType))
        {
            var enumValue = (int)Enum.Parse(enumType, name);
            var enumSchema = new OpenApiSchema
            {
                Type = "integer", Enum = new List<IOpenApiAny> { new OpenApiInteger(enumValue) }, Title = name
            };

            schema.OneOf.Add(enumSchema);
        }

        return Task.CompletedTask;
    }

    private string GetEnumDescription(Type type, string name)
    {
        var memberInfo = type.GetMember(name).FirstOrDefault();
        var attribute = memberInfo?.GetCustomAttribute<DescriptionAttribute>();
        return attribute?.Description ?? string.Empty;
    }
}

Which also used to work. But it no longer works and if you put a break point in, NotificationTypes is never one of the passed in types (nor is any other enum) yet the output openapi.json does have it listed as if it did process it.

@martincostello martincostello added feature-openapi area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc labels Oct 3, 2024
@captainsafia
Copy link
Member

This should be the base OpenApi 2 version that just has the text representation

If you're looking to get a textual representation of the enums that you can serialize, you can try using the JsonStringEnumConverter() to configure this behavior.

it should have OneOf syntax like OpenApi 3 has with Type, Enum value and Title (name)

I'm not super familiar with this pattern of using oneOf for representing enums. AFAIK, both v2 and v3 of the spec support the enum keyword. You mentioned 3.1 somewhere in your comment, that isn't officially support by this package or the underlying Microsoft.OpenApi package at the moment.

Ideally it should also inject x-ms-enum if you want on enums and give key/values since it's so widely supported by OpenAPI client generators.

Although, x-ms-enum may be popular, it's still a non-standard extension onto the spec which is why we don't generate it by default.

Which also used to work. But it no longer works and if you put a break point in, NotificationTypes is never one of the passed in types (nor is any other enum) yet the output openapi.json does have it listed as if it did process it.

Are you able to get a minimal repro with this issue? Also, do you happen to recall between which version change it stopped working?

@captainsafia captainsafia added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Oct 4, 2024
@JohnGalt1717
Copy link
Author

JohnGalt1717 commented Oct 4, 2024

If you're looking to get a textual representation of the enums that you can serialize, you can try using the JsonStringEnumConverter() to configure this behavior.

This is global and absolutely is not what I want for anywhere else. It should just work like Swashbuckle and every other environment and generate the enums list. This is baseline functionality and should (I'd say MUST) be the default behavior to generate this for even OpenAPI 2 support because the current behavior breaks other .NET stuff if you enable it to work as it should, and if you don't, gives you junk with no work around.

And right now I can't even intercept enums and do this myself like it should be because they don't even get passed in to IOpenApiSchemaTransformer.

Note, that nothing ever gets populated in OpenApiSchema schema in the TransformAsync method either, which it should because an enum is an enum and this should be populated as it is with Nswag and Swashbuckle in their equivalents.

it should have OneOf syntax like OpenApi 3 has with Type, Enum value and Title (name)

I'm not super familiar with this pattern of using oneOf for representing enums. AFAIK, both v2 and v3 of the spec support the enum keyword. You mentioned 3.1 somewhere in your comment, that isn't officially support by this package or the underlying Microsoft.OpenApi package at the moment.

They FINALLY fixed Enums in OpenAPI by using the oneof syntax for enums using OneOf to generate name, value, and description in oneof. This should be very high priority. Obviously if I can get enums into an IOpenApiSchemaTransformer as should be happening, I can add this myself, but this should be default behavior for 10.0 at the very least. (see also generics are now supported in OpenAPI 3.1 and .net should have full support too)

Ideally it should also inject x-ms-enum if you want on enums and give key/values since it's so widely supported by OpenAPI client generators.

Although, x-ms-enum may be popular, it's still a non-standard extension onto the spec which is why we don't generate it by default.

That's fine, however there's no way to manually generate it either because enums don't get passed into the IOpenApiSchemaTransformer interface.

Which also used to work. But it no longer works and if you put a break point in, NotificationTypes is never one of the passed in types (nor is any other enum) yet the output openapi.json does have it listed as if it did process it.

Are you able to get a minimal repro with this issue? Also, do you happen to recall between which version change it stopped working?

Just add an enum with key/values to an object that is referenced on a minimal api as the output of that api, and then add an IOpenApiSchemaTransformer and register it and put in a breakpoint. It fires for all primatives and even objects, but doens't fire for enums for some reason.

I tried going back and couldn't find where it broke.

@dotnet-policy-service dotnet-policy-service bot added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. labels Oct 4, 2024
@JohnGalt1717
Copy link
Author

JohnGalt1717 commented Oct 4, 2024

Update: I was able to hack around this:

The enum does get passed into the IopenApiSchemaTransformer, but the Enums array is empty when it shouldn't be so if you test based on this you won't be able to do anything.

This gets the enum properly so that you can fix (most) of what's wrong.

This does allow me to generate this:

      "NotificationTypes": {
        "enum": [
          "General"
        ],
        "type": "integer",
        "oneOf": [
          {
            "title": "General",
            "enum": [
              0
            ],
            "type": "integer"
          }
        ],
        "x-ms-enum": {
          "name": "NotificationTypes",
          "modelAsString": false,
          "values": [
            {
              "name": "General",
              "value": 0,
              "description": ""
            }
          ]
        }
      },

For anyone that needs this, here's an schema transformer that fixes what's discussed.

FixEnumsSchemaTransformer.zip

But there definitely is still a bug that Enum isn't being populated AND it isn't a viable workaround to change your JsonOptions globally just to fix even the basic case.

@captainsafia
Copy link
Member

@JohnGalt1717 I see what's going on. As long as your transformer is able to access the enum type and modify the schema, it should be good.

As mentioned in the other thread, support for OpenAPI v3.1 isn't included at the moment but hopefully will be in the future.

@captainsafia captainsafia added ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. and removed Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. labels Oct 4, 2024
Copy link
Contributor

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

@JohnGalt1717
Copy link
Author

Even without OpenAPI 3.1, this should at least produce OpenAPI 2.0 by default, automatically without intervention. That means that the enums list should be populated automatically and independently of any global json settings. Please reopen as the root issue isn't resolved.

@captainsafia
Copy link
Member

this should at least produce OpenAPI 2.0 by default

We don't produce OpenApi v2.0 by default, we produce OpenApi v3.0 since that's much newer. You can change the version used via options though.

@JohnGalt1717
Copy link
Author

The enums list is also part of V3.0 standard and should be populated in that case too.

@deinok
Copy link

deinok commented Nov 19, 2024

This OpenApi implementation is a disaster in comparation of Swashbuckle. Everything related to enums does not work as expected.

As a user you cant expect that this needs a lot of workarrounds

@ZimM-LostPolygon
Copy link

Had to implement OpenAPI generation for the first time. Obviously I jumped straight to the Microsoft.OpenApi package. After battling with all kinds of issues of issues, I tried NSwag and everything just immediately worked with sensible defaults, including emitting enums as actual enums.

I would really want to rely on an officially supported solution, but it's just not there.

@niemyjski
Copy link

Agreed! Upgrading from Swashbuckle and it's so inconsistent (also weird it doesn't support EnumMember)....

public enum BillingStatus
{
    Trialing = 0,
    Active = 1
}

Previous

{
"BillingStatus": {
                "enum": [
                    0,
                    1
                ],
                "type": "integer",
                "description": "\n\n0 = Trialing\n\n1 = Active",
                "format": "int32",
                "x-enumNames": [
                    "Trialing",
                    "Active"
                ]
            }
}

New

{
 "BillingStatus": {
                "type": "integer"
            }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved
Projects
None yet
Development

No branches or pull requests

6 participants