最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - OpenAPI String Enum Schema creation with .NET 9 - Stack Overflow

programmeradmin1浏览0评论

I'm trying to get proper enum generation via OpenAPI in my ASP.NET Core 9 project so I can create a typescript schema using

npx openapi-typescript https://localhost:7088/openapi/v1.json -o ./apiSchema.ts --enum

Since the default OpenAPI implementation in .NET 9 does only generate enums as integers, I looked into schema transformers. However I did not fully find out how to use them nor found docs which helped me with the issue that it doubles up the schema.

var enumCache = new Dictionary<Type, OpenApiSchema>();
options.AddSchemaTransformer(async delegate(
    OpenApiSchema schema,
    OpenApiSchemaTransformerContext context,
    CancellationToken ct)
{
    if (context?.JsonPropertyInfo?.PropertyType.IsEnum == true)
    {
        if (enumCache.TryGetValue(context.JsonPropertyInfo.PropertyType, out var enumSchema))
        {
            Console.WriteLine($"prop: {context?.JsonPropertyInfo?.Name}: enumSchema:{enumSchema} - from cache");
            schema = enumSchema;
            return;
        }

        var enumNames = Enum.GetNames(context.JsonPropertyInfo.PropertyType);
        var enumValues = Enum.GetValues(context.JsonPropertyInfo.PropertyType);
        var enumDesc = enumValues.Cast<object>().Select(
            (value, index) =>
            {
                // get description attribute via reflection
                var attr = context.JsonPropertyInfo.PropertyType.GetField(value.ToString())?
                    .GetCustomAttribute<DescriptionAttribute>();

                return new
                {
                    Value = (int) value,
                    Name = enumNames[index],
                    Description = attr?.Description
                };
            });

        var openApiValueArray = new OpenApiArray();
        var openApiNameArray = new OpenApiArray();
        var openApiDescArray = new OpenApiArray();
        foreach (var item in enumDesc)
        {
            openApiValueArray.Add(new OpenApiInteger(item.Value));
            openApiNameArray.Add(new OpenApiString(item.Name));
            openApiDescArray.Add(new OpenApiString(item.Description));
        }

        schema.Extensions.Add("x-enum-varnames", openApiNameArray); // /advanced#enum-extensions
        schema.Extensions.Add("x-enum-descriptions", openApiDescArray);
        schema.Extensions.Add("enum", openApiValueArray);

        enumCache[context.JsonPropertyInfo.PropertyType] = schema;
        
        return;
    }

    return;
});

This is an (partial) example where the types double up. This also affects all containing types and the endpoints then use the wrong "old" schema.

"WorldStatus": {
    "type": "integer",
    "x-enum-varnames": [
      "Active",
      "Maintenance",
      "Full",
      "Ended"
    ],
    "x-enum-descriptions": [
      null,
      null,
      null,
      "game over"
    ],
    "enum": [
      0,
      1,
      2,
      3
    ]
  },
  "WorldStatus2": {
    "type": "integer"
  }

Test enum:

public enum WorldStatus
{
    Active,
    Maintenance,
    Full,

    [Description("game over")]
    Ended
}

So my question is on how to remove the default/original schema. I have the feeling there needs to be an easier way then also creating a OperationTransformer which check each type and its inner types.

I'm trying to get proper enum generation via OpenAPI in my ASP.NET Core 9 project so I can create a typescript schema using

npx openapi-typescript https://localhost:7088/openapi/v1.json -o ./apiSchema.ts --enum

Since the default OpenAPI implementation in .NET 9 does only generate enums as integers, I looked into schema transformers. However I did not fully find out how to use them nor found docs which helped me with the issue that it doubles up the schema.

var enumCache = new Dictionary<Type, OpenApiSchema>();
options.AddSchemaTransformer(async delegate(
    OpenApiSchema schema,
    OpenApiSchemaTransformerContext context,
    CancellationToken ct)
{
    if (context?.JsonPropertyInfo?.PropertyType.IsEnum == true)
    {
        if (enumCache.TryGetValue(context.JsonPropertyInfo.PropertyType, out var enumSchema))
        {
            Console.WriteLine($"prop: {context?.JsonPropertyInfo?.Name}: enumSchema:{enumSchema} - from cache");
            schema = enumSchema;
            return;
        }

        var enumNames = Enum.GetNames(context.JsonPropertyInfo.PropertyType);
        var enumValues = Enum.GetValues(context.JsonPropertyInfo.PropertyType);
        var enumDesc = enumValues.Cast<object>().Select(
            (value, index) =>
            {
                // get description attribute via reflection
                var attr = context.JsonPropertyInfo.PropertyType.GetField(value.ToString())?
                    .GetCustomAttribute<DescriptionAttribute>();

                return new
                {
                    Value = (int) value,
                    Name = enumNames[index],
                    Description = attr?.Description
                };
            });

        var openApiValueArray = new OpenApiArray();
        var openApiNameArray = new OpenApiArray();
        var openApiDescArray = new OpenApiArray();
        foreach (var item in enumDesc)
        {
            openApiValueArray.Add(new OpenApiInteger(item.Value));
            openApiNameArray.Add(new OpenApiString(item.Name));
            openApiDescArray.Add(new OpenApiString(item.Description));
        }

        schema.Extensions.Add("x-enum-varnames", openApiNameArray); // https://openapi-ts.dev/advanced#enum-extensions
        schema.Extensions.Add("x-enum-descriptions", openApiDescArray);
        schema.Extensions.Add("enum", openApiValueArray);

        enumCache[context.JsonPropertyInfo.PropertyType] = schema;
        
        return;
    }

    return;
});

This is an (partial) example where the types double up. This also affects all containing types and the endpoints then use the wrong "old" schema.

"WorldStatus": {
    "type": "integer",
    "x-enum-varnames": [
      "Active",
      "Maintenance",
      "Full",
      "Ended"
    ],
    "x-enum-descriptions": [
      null,
      null,
      null,
      "game over"
    ],
    "enum": [
      0,
      1,
      2,
      3
    ]
  },
  "WorldStatus2": {
    "type": "integer"
  }

Test enum:

public enum WorldStatus
{
    Active,
    Maintenance,
    Full,

    [Description("game over")]
    Ended
}

So my question is on how to remove the default/original schema. I have the feeling there needs to be an easier way then also creating a OperationTransformer which check each type and its inner types.

Share Improve this question edited Mar 11 at 18:19 marc_s 756k184 gold badges1.4k silver badges1.5k bronze badges asked Mar 11 at 13:12 FritzFritz 94310 silver badges32 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

I don't know about this feature, but I don't think this would work:

if (enumCache.TryGetValue(context.JsonPropertyInfo.PropertyType, out var enumSchema))
{
    schema = enumSchema;
    return;
}

You are assigning a value to the argument, which will only be passed back to the caller if the argument was passed by reference (ref or out). Which it doesn't seem to be. So all this does is change the object the argument variable is referencing. You should be instead modifying the schema variable as you do in the rest of the method.

The issue was my caching as @juunas pointed out. I did not notice until his answer. It just started to work when I implemented it as Transformer class and left the caching unpropusfully out.

My final (working) solution:

options.AddSchemaTransformer<EnumSchemaTransformer>();
public class EnumSchemaTransformer : Microsoft.AspNetCore.OpenApi.IOpenApiSchemaTransformer
{
    public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
    {
        if (context?.JsonPropertyInfo?.PropertyType.IsEnum == true)
        {
            var enumType = context.JsonPropertyInfo.PropertyType;
            var enumValues = Enum.GetValues(enumType).Cast<object>();
            var enumDesc = enumValues.Select(value =>
            {
                var field = enumType.GetField(value.ToString());

                return new
                {
                    Value = (int)value,
                    field?.Name,
                    field?.GetCustomAttribute<DescriptionAttribute>()?.Description
                };
            });

            var openApiValueArray = new OpenApiArray();
            var openApiNameArray = new OpenApiArray();
            var openApiDescArray = new OpenApiArray();
            foreach (var item in enumDesc)
            {
                openApiValueArray.Add(new OpenApiInteger(item.Value));
                openApiNameArray.Add(new OpenApiString(item.Name));
                openApiDescArray.Add(new OpenApiString(item.Description));
            }
            schema.Extensions.Add("x-enum-varnames", openApiNameArray);
            schema.Extensions.Add("x-enum-descriptions", openApiDescArray);
            schema.Extensions.Add("enum", openApiValueArray);

            return Task.CompletedTask;
        }

        return Task.CompletedTask;
    }
}
发布评论

评论列表(0)

  1. 暂无评论