Skip to content
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

Proper generation of c# enums using the the OpenAPI 3.x spec #1387

Closed
ghost opened this issue Nov 21, 2019 · 4 comments
Closed

Proper generation of c# enums using the the OpenAPI 3.x spec #1387

ghost opened this issue Nov 21, 2019 · 4 comments

Comments

@ghost
Copy link

ghost commented Nov 21, 2019

Thanks for contributing to Swashbuckle.AspNetCore! As per the contributing guidelines, please adhere to the following rules of thumb before submitting your issue:

  1. If it's not a bug report, feature request or PR, don't submit it here. Post to Stackoverflow instead.
  2. If your issue is specifically a UI concern, don't submit it here. Post to the swagger-ui repo.
  3. For bug reports, specify the version your using and provide clear repro steps.

I'm using Swashbuckle.AspNetCore -Version 5.0.0-rc4

In my case I have the following issue:

I have a C# enum defined like so:
public enum ObjectState
{
Unchanged = 0,
Add = 1,
Update = 2,
Delete = 3
}

This get represented in the OpenAPI 3.x doc as
"components": {
"schemas": {
"ObjectState": {
"enum": [
0,
1,
2,
3
],
"type": "integer",
"format": "int32"
},

When I run the NSwag client generator all it has as a reference is the OpenAPI spec which of course is missing the name portion of the enum. NSwag generates the following object for the client which is basically unusable:

public enum ObjectState
{
    _0 = 0,
    _1 = 1,
    _2 = 2,
    _3 = 3
}

Would it be possible to use oneOf from the OpenAPI spec instead to properly represent a C# enum
See the discussion here:
OAI/OpenAPI-Specification#681

According to handrews the spec is not going to be changed regarding this issue and suggested using something like the following from the OpenAPI spec to represent the enum.
oneOf:

  • enum: [1]
    title: Sunny
    description: Blue sky
  • enum: [2]
    title: Cloudy
    description: Slightly overcast
  • enum: [3]
    title: Rainy
    description: Take an umbrella with you

Thoughts? Work arounds???
Much appreciated
Kevin

@ghost ghost changed the title Proper support of c# enums in the OpenAPI 3.x spec Proper gneration of c# enums using the the OpenAPI 3.x spec Nov 21, 2019
@ghost ghost changed the title Proper gneration of c# enums using the the OpenAPI 3.x spec Proper generation of c# enums using the the OpenAPI 3.x spec Nov 21, 2019
@ghost
Copy link
Author

ghost commented Nov 30, 2019

I see there is a solution to this problem using Swashbuckle.AspNetCore.Filters so I'm closing.

@ghost ghost closed this as completed Nov 30, 2019
@RouR
Copy link

RouR commented Dec 31, 2019

Can you provide solution?

@WhitWaldo
Copy link

@RouR I use the following (would require a little tweaking to yield the original ask above):

[DataContract]
public enum MyEnum {
  [EnumMember(Value="red")]
  Red,
  [EnumMember(Value="blue"]
  Blue
}

The filter itself:

public class DescribeEnumMemberValues : ISchemaFilter
    {
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            if (context.Type.IsEnum)
            {
                schema.Enum.Clear();

                //Retrieve each of the values decorated with an EnumMember attribute
                foreach (var member in context.Type.GetMembers())
                {
                    var memberAttr = member.GetCustomAttributes(typeof(EnumMemberAttribute), false).FirstOrDefault();
                    if (memberAttr != null)
                    {
                        var attr = (EnumMemberAttribute) memberAttr;
                        schema.Enum.Add(new OpenApiString(attr.Value));
                    }
                }
            }
        }
    }

And in your Startup, register the filter with:

services.AddSwaggerGen(c => {
  c.SchemaFilter<DescribeEnumMemberValues>();
});

The resulting enum will be described using the values within the EnumMember attribute values instead of their indices.

@martinRocks
Copy link

I wish there was a better way to do this, but using WhitWaldo solution I came up with this. I like it a bit more because it uses the Description attribute.

And in your Startup, register the filter with:

services.AddSwaggerGen(c => {
  c.SchemaFilter<DescribeEnumMemberValues>();
});
public enum ConfidenceEnum
{
    [Description("Very xx")]
    Very = 1,

    [Description("SomeWhat xx")]
    SomeWhat,

    [Description("Low xx")]
    Low
}
public class DescribeEnumMemberValues : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (context.Type.IsEnum)
        {
            var dictionary = Enum.GetValues(context.Type)
                .Cast<object>()
                .ToDictionary(value => (int)value, value => GetEnumDescription((Enum)value));
            schema.Description = "Enum values:" + string.Join(", ", dictionary.Select(x => $"{x.Key} = {x.Value}"));
        }
    }

    private static string GetEnumDescription(Enum value)
    {
        var fi = value.GetType().GetField(value.ToString());

        if (fi == null) return value.ToString(); 
        var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

        return attributes.Length > 0 ? attributes[0].Description : value.ToString(); 
    }
}

It produces this:

"ConfidenceEnum": {
                "enum": [
                    1,
                    2,
                    3
                ],
                "type": "integer",
                "description": "Enum values:1 = Very xx, 2 = SomeWhat xx, 3 = Low xx",
                "format": "int32"
            }

This issue was closed.
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

No branches or pull requests

3 participants