Search code examples
c#nswag

Expose string as enum with NSwag


I have this controller

public class Person
{
   public int Id { get; set; }
   public string Position { get; set; }
}

[ApiController]
[Route("[controller]")]
public class MyCtrl : ControllerBase
{
   [HttpGet("{id}")]
   public ActionResult<Person> Get(int id)
   {
      // load person with ID == id from db
      return new ActionResult<Person>(person);
   }
}

Now, the Position is just a string, but based on configuration in the database, it can only be a set of finite values. I would really like if I could mimic that Position was an enum in the Swagger interface (I use NSwag).

I would like if the API looked like it had been generated from this (where PositionType is generated from the configuration in the DB):

public enum PositionType
{
   Student,
   Teacher
}

public class Person
{
   public int Id { get; set; }
   public PositionType Position { get; set; }
}

Solution

  • I ended up doing this.

    [AttributeUsage(AttributeTargets.Property)]
    public class PositionEnum : Attribute
    {
    }
    
    public class Person
    {
       public int Id { get; set; }
       [PositionEnum]
       public string Position { get; set; }
    }
    
    public class PositionEnumProcessor : ISchemaProcessor
    {
        public void Process(SchemaProcessorContext context)
        {
            var propertyInfos =
                context.ContextualType.Properties
                    .Where(x => Attribute.IsDefined(x.PropertyInfo, typeof(PositionEnum)))
                    .ToList();
            if (!propertyInfos.Any())
            {
                return;
            }
            var jsonSchema = context.Generator.Generate(typeof(PositionType), context.Resolver);
            var containerSchema = JsonSchema.FromType<object>();
            containerSchema.Type = JsonObjectType.None;
            containerSchema.Reference = jsonSchema;
            foreach (var propertyInfo in propertyInfos)
            {
                var camelCasePropertyName = System.Text.Json.JsonNamingPolicy.CamelCase.ConvertName(propertyInfo.Name);
                if (context.Schema.Properties.TryGetValue(camelCasePropertyName, out var schemaProperty))
                {
                    schemaProperty.Type = JsonObjectType.None;
                    schemaProperty.OneOf.Add(containerSchema);
                }
    
            }
        }
    }
    

    Then in builder.Services.AddOpenApiDocument() I have added this:

    ...
    openApiConfiguration.SchemaSettings.SchemaProcessors.Add(new PositionEnumProcessor());
    ...
    

    If anyone has a more elegant solution, please let me know 😊