Maks Knoski
Maks Knoski

Reputation: 51

Swashbuckle - treat string as enum in swagger documentation

I'm using FluentValidation and I want to receive only my custom errors from fluent validators. That's why all my properties in requests classes are strings. However, I would also like to have better documentation for enum types.

Here is my sample request:

 public class AddNewPaymentRequest
 {
     [EnumDataType(typeof(PaymentStatus))]
     public string PaymentStatus { get; set; }

     public string Id { get; set; }
 }

And sample enum:

 public enum PaymentStatus
 {
     Unknown,
     New,
     Pending,
     Completed
 }

And controller:

[HttpPost]
public async Task<ActionResult> PostAsync([FromBody] AddNewPaymentRequest request)
{
   ...
}

I'd like to have this EnumDataType working as an information for Swagger to display enum description, instead of string destripction. I want Swagger to treat this string as an enum.

Actual result: click

Expected result: click

Is there any possibility to configure Swashbuckle in this way?

Upvotes: 3

Views: 13534

Answers (3)

Michael Ziluck
Michael Ziluck

Reputation: 665

These answers all effectively say "you can't do that, here are other options." Well, you can do it and here's how.

Create an attribute named OpenApiEnumAttribute like so:

/// <summary>
/// Used in conjunction with OpenApiEnumSchemaFilter to apply the enum property
/// to an OpenAPI schema.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class OpenApiEnumAttribute : Attribute
{
    public OpenApiEnumAttribute(params string[] enumOptions)
    {
        EnumOptions = enumOptions;
    }
    
    public OpenApiEnumAttribute(Type enoom)
    {
        if (!enoom.IsEnum)
        {
            throw new ArgumentException("Type not an enum", nameof(enoom));
        }
        EnumOptions = Enum.GetNames(enoom);
    }
    
    /// <summary>
    /// Options for values that the property can have
    /// </summary>
    public string[] EnumOptions { get; }
}

Then also create a Schema Filter like so:

using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

/// <summary>
/// Applies the enum property to an OpenAPI schema. Most useful to constrain
/// strings to be a specific value.
/// </summary>
public class OpenApiEnumSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (context.MemberInfo == null)
        {
            return;
        }
        var enumAnnotation = context.MemberInfo.GetCustomAttributes(typeof(OpenApiEnumAttribute), false)
            .Cast<OpenApiEnumAttribute>()
            .FirstOrDefault();

        if (enumAnnotation == null) return;

        schema.Enum = enumAnnotation.EnumOptions.Select(option => new OpenApiString(option))
            .Cast<IOpenApiAny>().ToList();
    }
}

Finally in your Startup.cs (or wherever you call AddSwaggerGen:

services.AddSwaggerGen(config => { config.SchemaFilter<OpenApiEnumSchemaFilter>(); } );

Based on your example you can now use it like so:

public class AddNewPaymentRequest
{
    [OpenApiEnum(typeof(PaymentStatus))]
    public string PaymentStatus { get; set; }

    public string Id { get; set; }
}

Upvotes: 0

andrew.fox
andrew.fox

Reputation: 7941

Swaggers supports only RequiredAttribute, ObsoleteAttribute and MetaData (for external class). So there's no way to describe accepted values besides writing something like this:

/// <summary> Allowed values are....

Upvotes: 1

monty
monty

Reputation: 8755

I guess we are talking about .NET Core

If it is ok for you it would be a possibility to define it explicity as enum instead of string

 public class AddNewPaymentRequest
 {
     //[EnumDataType(typeof(PaymentStatus))]
     //public string PaymentStatus { get; set; }

     public PaymentStatus PaymentStatus { get; set; }

     public string Id { get; set; }
 }

Then you could define how SwaggerGen treats the enums

 services.AddSwaggerGen(c => {c.DescribeAllEnumsAsStrings(); } );

If you run into issues with the serialized/deserialzed enums (by Newtonsoft.Json) when receiving/sending data at/from you api you can take more controller about the conversion (if necessary)

 [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
 public enum PaymentStatus
 {
     [EnumMember(Value = "Unknwon")]
     Unknown,

     [EnumMember(Value = "New")]
     New,

     [EnumMember(Value = "Pending")]
     Pending,

     [EnumMember(Value = "Completed")]
     Completed,

     [EnumMember(Value = "something_different_with_underline")]
     SomethingDifferentWithUnderline
 }

Mind that ToString() for these enums may result in different strings (with/without) underlines

Upvotes: 3

Related Questions