8

I have two applications A and B. On A I use swagger for describing an API. On A I have also the definitions of classes with some enums properties. I want to generate a client API on B. For this I use Autorest. Everything works fine except one thing - enums. For some reason the enums are not generated properly and a type of properties (which were originally enum) are type of string or int (depends on the use of DescribeAllEnumsAsStrings(). I used it in example below so it is string in this case).

Enum definition generated JSON by swagger:

        "Car": {
        "properties": {
            "color": {
                "enum": [
                    "Red",
                    "Blue",
                    "Green"
                ],
                "type": "string"
            }
        },
        "type": "object"
    }

Swagger registraion in Startup.cs

// Register the Swagger generator, defining one or more Swagger documents
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info { Title = "Car API", Version = "v1" });
            c.AddSecurityDefinition("Bearer", new ApiKeyScheme { In = "header", Description = "description", Name = "Authorization", Type = "apiKey" });
            c.DescribeAllEnumsAsStrings();
        });

Autorest command:

autorest --input-file=http://localhost:55448/swagger/v1/swagger.json --csharp --namespace=Online.Integrations.Api.Service.Car --output-folder=. 

Generated class (string instead of enum):

    public partial class Car
{
    /// <summary>
    /// Initializes a new instance of the Car class.
    /// </summary>
    public Car()
    {
      CustomInit();
    }

    /// <summary>
    /// Initializes a new instance of the Car class.
    /// </summary>
    /// <param name="application">Possible values include: 'Any', 'Web'
    /// ...
    /// </param>
    public Car(string color = default(string))
    {
        Color = color;
    }

    /// <summary>
    /// An initialization method that performs custom operations like setting defaults
    /// </summary>
    partial void CustomInit();

    /// <summary>
    /// </summary>
    [JsonProperty(PropertyName = "color")]
    public string Color { get; set; }
}

I found this: https://github.com/Azure/autorest/tree/master/docs/extensions#x-ms-enum

there is some x-ms-enum extension but i have no idea how use it. I tryed edit JSON definition manually (what i did in example below) but it was also unsuccessful. Generated class contains object color = default(object) instead of string as in first example, not enum.

    "color": {
            "type": "string",
            "enum": [
                "Red",
                "Blue",
                "Green"
            ],
            "x-ms-enum": {
                "name": "Color",
                "modelAsString": false
     },

I'll be glad for any help. Thanks!

Edit: 1) Full Swagger definition (result type : Sting):

In swagger def. is "type": "string" right after enum definition

 {"swagger":"2.0","info":{"version":"v1","title":"Audit Overseer API"},"basePath":"/","paths":{"/api/Audit":{"get":{"tags":["Audit"],"operationId":"ApiAuditGet","consumes":[],"produces":["text/plain","application/json","text/json"],"parameters":[{"name":"Id","in":"query","required":false,"type":"string"},{"name":"Application","in":"query","required":false,"type":"string","enum":["Any","Alfa","Beta"]},{"name":"DatabaseName","in":"query","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Transaction"}}}}}}},"definitions":{"Transaction":{"type":"object","properties":{"callId":{"type":"string"},"actionName":{"type":"string"},"application":{"enum":["Any","Alfa","Beta"],"type":"string"},"httpStatusCode":{"type":"string"},"dateTime":{"format":"date-time","type":"string"}}}},"securityDefinitions":{"Bearer":{"name":"Authorization","in":"header","type":"apiKey","description":"Please insert JWT with Bearer into field"}}}

autorest execution:

autorest execution result:

result

2) Full Swagger definition (result type : Object):

In swagger def. my experiment with "x-ms-enum" right after enum definition

{"basePath":"/","definitions":{"Transaction":{"properties":{"actionName":{"type":"string"},"application":{"enum":["Any","Alfa","Beta"],"x-ms-enum":{"modelAsString":false,"name":"Application"}},"callId":{"type":"string"},"dateTime":{"format":"date-time","type":"string"},"httpStatusCode":{"type":"string"}},"type":"object"}},"info":{"title":"Audit Overseer API","version":"v1"},"paths":{"/api/Audit":{"get":{"consumes":[],"operationId":"ApiAuditGet","parameters":[{"in":"query","name":"Id","required":false,"type":"string"},{"enum":["Any","Alfa","Beta"],"in":"query","name":"Application","required":false,"type":"string"},{"in":"query","name":"DatabaseName","required":false,"type":"string"}],"produces":["text/plain","application/json","text/json"],"responses":{"200":{"description":"Success","schema":{"items":{"$ref":"#/definitions/Transaction"},"type":"array"}}},"tags":["Audit"]}}},"securityDefinitions":{"Bearer":{"description":"Please insert JWT with Bearer into field","in":"header","name":"Authorization","type":"apiKey"}},"swagger":"2.0"}

...or I can keep it empty. Result is same

{"basePath":"/","definitions":{"Transaction":{"properties":{"actionName":{"type":"string"},"application":{"enum":["Any","Alfa","Beta"]},"callId":{"type":"string"},"dateTime":{"format":"date-time","type":"string"},"httpStatusCode":{"type":"string"}},"type":"object"}},"info":{"title":"Audit Overseer API","version":"v1"},"paths":{"/api/Audit":{"get":{"consumes":[],"operationId":"ApiAuditGet","parameters":[{"in":"query","name":"Id","required":false,"type":"string"},{"enum":["Any","Alfa","Beta"],"in":"query","name":"Application","required":false,"type":"string"},{"in":"query","name":"DatabaseName","required":false,"type":"string"}],"produces":["text/plain","application/json","text/json"],"responses":{"200":{"description":"Success","schema":{"items":{"$ref":"#/definitions/Transaction"},"type":"array"}}},"tags":["Audit"]}}},"securityDefinitions":{"Bearer":{"description":"Please insert JWT with Bearer into field","in":"header","name":"Authorization","type":"apiKey"}},"swagger":"2.0"}

(now lets execute same autorest command as before)

and result is:

enter image description here

Kool
  • 323
  • 1
  • 3
  • 10
  • Have you specified `"consumes": ["application/json"]` for this API? It is a known quirk of AutoRest (left untouched for backwards compatibility) to keep its fingers from any non-built-in/non-primitive types if no such `consumes` is specified. If that doesn't help, please provide your full Swagger so I can reproduce. :) – olydis Sep 18 '17 at 16:43
  • Hello, thank you for Your response. I tryed manually add it but it does not help. I put full Swagger json definiton into question of this topic. Please look at it. Thank you for Your help! – Kool Sep 22 '17 at 08:32
  • Using `x-ms-enum` worked out of the box for me exactly as you pasted above (I applied it to then enums with `Any, Alfa, Beta`). Post the Swagger that you *expect* to work but get `object` if you need further assistance. – olydis Sep 22 '17 at 16:45
  • Okay I just add my exact approach with the results I mentioned before. If I am correct - You wrote something about application of x-ms-enum, If I am correct, can You please show me how? And please, can You show me your result with correct generated enums? I would like to see it on my own eyes. Thank You for your assistance! I appreciate that. – Kool Oct 02 '17 at 11:26
  • The two documents you posted are identical, i.e. there is no `x-ms-enum` in either of them. See https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-enum for more information about it. – olydis Oct 09 '17 at 17:03
  • Sorry, it was a mistake. Now, first one contains x-ms-emum def. Result is object. – Kool Oct 18 '17 at 10:17
  • 1
    `"type": "string"` missing on property `application` - after adding that, it generates fine for me – olydis Oct 21 '17 at 01:21
  • 1
    It works. Awesome! Is there a way how to genereate value **x-ms-enum** value automaticly by swagger? Or i have to add it manually into json? Thank you! – Kool Oct 23 '17 at 07:47
  • You essentially have to add it manually, after all what `name` should be chosen automatically? :) – olydis Oct 24 '17 at 22:51
  • From my perspective it should be original name of Enum.. or no? – Kool Oct 25 '17 at 09:55
  • not sure what "original name of Enum" is - maybe you mean the name used in the `definitions` section? Note that that is only *one* of many places where enums can occur, in the other places there is no information about a reasonable name available :-( so we opted for consistency and expect a `name` everywhere – olydis Oct 26 '17 at 19:04
  • I am sure that I cant see all available options where `name` can be defined. In my case `name` is always in same position. As you wrote - in `definitions` section and always in same level of json where any of `enum` is defined. I will try to write some script for this. Mby it would be better how describe what i mean :-) – Kool Oct 27 '17 at 08:41

4 Answers4

11

It seems like you could get SwaggerGen to automatically add the x-ms-enum extension by using the SchemaFilter<TFilter> option method.

Swagger registration in Startup.cs

// Register the Swagger generator, defining one or more Swagger documents
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info { Title = "Car API", Version = "v1" });
            c.AddSecurityDefinition("Bearer", new ApiKeyScheme { In = "header", Description = "description", Name = "Authorization", Type = "apiKey" });
            c.ApplyFiltersToAllSchemas();//ignore deprecation warning
            c.SchemaFilter<EnumFilter>();
            c.DescribeAllEnumsAsStrings();
        });

Then define an EnumFilter class with the following code

public class EnumFilter : ISchemaFilter
{
    public void Apply(Schema model, SchemaFilterContext context)
    {
        if (model == null)
            throw new ArgumentNullException("model");

        if (context == null)
            throw new ArgumentNullException("context");

        if (context.SystemType.IsEnum)
            model.Extensions.Add("x-ms-enum", new
            {
                name = context.SystemType.Name,
                modelAsString = false
            });
    }
}
OzBob
  • 4,227
  • 1
  • 39
  • 48
  • This works for enums properties in a class, but it doesn't seem to do anything if there's an enum as a parameter. The parameter ends up being a string. – Gabriel Luci Apr 20 '18 at 14:27
  • It appears that's a bug: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/368 – Gabriel Luci Apr 20 '18 at 15:39
  • 1
    Looks like parameters need to be addressed using parameters filters. https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/707 – Richard McDaniel Jul 10 '19 at 14:02
  • Works fine. I just recommend to add: `model.Enum = Enum.GetNames(context.SystemType);` because swagger ignore values in enum. So if You dont have values ordered then you get different order from autorest – Kool Sep 25 '19 at 09:35
2

Looked hours to find a solution for nullable enumerations!

public class EnumFilter : ISchemaFilter
{

    public void Apply(Schema model, SchemaRegistry schemaRegistry, Type type)
    {
        if (model == null)
        {
            throw new ArgumentNullException("model");
        }

        if (schemaRegistry == null)
        {
            throw new ArgumentNullException("schemaRegistry");
        }

        if (IsEnum(type, out var enumName))
        {
            model.vendorExtensions.Add("x-ms-enum", new
                                              {
                                                  name = enumName ?? type.Name,
                                                  modelAsString = false
                                              });

        }
    }

    public static bool IsEnum(Type t, out string enumName)
    {
        if (t.IsEnum)
        {
            enumName = t.Name;
            return true;
        }
        Type u = Nullable.GetUnderlyingType(t);
        enumName = u?.Name;
        return (u != null) && u.IsEnum;
    }
}
Wuedmo
  • 21
  • 2
2

Future proofing solution, further to @richard-mcdaniel solution, once the outstanding enum values issue on the autorest repo get's fixed, here is the values of enums section to the EnumFilter:ISchemaFilter

Swagger registration in Startup.cs

// Register the Swagger generator, defining one or more Swagger documents
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info { Title = "Car API", Version = "v1" });
            c.AddSecurityDefinition("Bearer", new ApiKeyScheme { In = "header", Description = "description", Name = "Authorization", Type = "apiKey" });
            c.ApplyFiltersToAllSchemas();//ignore deprecation warning
            c.SchemaFilter(() => new EnumTypeSchemaFilter(false));
            c.DescribeAllEnumsAsStrings();
        });

Then define an EnumFilter class with the following code:

using Swashbuckle.Swagger;
using System;

namespace Swashbuckle.AutoRestExtensions
{
    public class EnumTypeSchemaFilter : ISchemaFilter
    {
        public EnumTypeSchemaFilter()
        {
        }

        public EnumTypeSchemaFilter(bool modelAsString)
        {
            _modelAsString = modelAsString;
        }

        public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
        {
            type = Nullable.GetUnderlyingType(type) ?? type;
            System.Diagnostics.Debug.WriteLine(type.FullName);
            if (type.IsEnum)
            {
                // Add enum type information once
                if (schema.vendorExtensions.ContainsKey("x-ms-enum")) return;

                if (_modelAsString)
                {
                    schema.vendorExtensions.Add("x-ms-enum", new
                    {
                        name = type.Name,
                        modelAsString = _modelAsString
                    });
                }
                else
                {
                    var valuesList = new System.Collections.Generic.List<object>();
                    foreach (var fieldInfo in type.GetFields())
                    {
                        if (fieldInfo.FieldType.IsEnum)
                        {
                            var fName = fieldInfo.Name;
                            var fValue = (int)fieldInfo.GetRawConstantValue();
                            valuesList.Add(new { value = fValue, description = fName, name = fName });
                        }
                    }
                    schema.vendorExtensions.Add("x-ms-enum", new
                    {
                        name = type.Name,
                        modelAsString = _modelAsString,
                        values = valuesList
                        //Values:
                        /*
                        accountType:
                          type: string
                          enum:
                          - Standard_LRS
                          - Standard_ZRS
                          - Standard_GRS
                          - Standard_RAGRS
                          - Premium_LRS
                          x-ms-enum:
                            name: AccountType
                            modelAsString: false
                            values:
                            - value: Standard_LRS
                              description: Locally redundant storage.
                              name: StandardLocalRedundancy
                            - value: Standard_ZRS
                              description: Zone-redundant storage.
                            - value: Standard_GRS
                              name: StandardGeoRedundancy
                            - value: Standard_RAGRS
                            - value: Premium_LRS
                        */
                    });
                }
            }
        }

        private readonly bool _modelAsString;
    }
}
OzBob
  • 4,227
  • 1
  • 39
  • 48
1

I fixed this with that swagger setup

services.AddSwaggerGenNewtonsoftSupport();
services.AddSwaggerGen(x => x.UseInlineDefinitionsForEnums());

enter image description here

malisasmaz
  • 99
  • 1
  • 7