为什么 autorest 用 Swagger 中的对象替换我的自定义结构?

Why autorest is replacing my custom struct by an object in Swagger?

我创建了一个自定义 readonly struct 来定义一个不可变的值类型,我称之为 TenantId:

[DebuggerDisplay("ID={m_internalId.ToString()}")]
[JsonConverter(typeof(TenantIdJsonConverter))]
public readonly struct TenantId : IEquatable<TenantId>
{
    private readonly Guid m_internalId;

    public static TenantId New => new(Guid.NewGuid());

    private TenantId(Guid id)
    {
        m_internalId = id;
    }

    public TenantId(TenantId otherTenantId)
    {
       m_internalId = otherTenantId.m_internalId;
    }

    ...
}

我还定义了一个名为 PurchaseContract 的合同,它是 HTTP 响应的一部分:

[JsonObject(MemberSerialization.OptIn)]
public sealed class PurchaseContract
{
    [JsonProperty(PropertyName = "tenantId")]
    public TenantId TenantId { get; }
        
    [JsonProperty(PropertyName = "total")]
    public double Total { get; }
}

最后,我设置了一个 HTTP 触发函数,它将 return PurchaseContract 的一个实例。目前,它已在 ProducesResponseTypeAttribute:

中进行了描述
[ApiExplorerSettings(GroupName = "Purchases")]
[ProducesResponseType(typeof(PurchaseContract), (int) HttpStatusCode.OK)]
[FunctionName("v1-get-purchase")]
public Task<IActionResult> RunAsync
(
    [HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "v1/purchases")]
    HttpRequest httpRequest,
    [SwaggerIgnore] 
            ClaimsPrincipal claimsPrincipal
)
{
    //  Stuff to do.
    return Task.FromResult((IActionResult)new OkResult());
}

在我的 Startup class 中,我是这样设置 swagger 的:

private static void ConfigureSwashBuckle(IFunctionsHostBuilder functionsHostBuilder)
{
    functionsHostBuilder.AddSwashBuckle(Assembly.GetExecutingAssembly(), options =>
            {
                options.SpecVersion = OpenApiSpecVersion.OpenApi3_0;
                options.AddCodeParameter = true;
                options.PrependOperationWithRoutePrefix = true;
                options.XmlPath = "FunctionApp.xml";
                options.Documents = new []
                {
                    new SwaggerDocument
                    {
                        Title = "My API,
                        Version = "v1",
                        Name = "v1",
                        Description = "Description of my API",
                    }
                };
            });
        }

在swagger UI页面中,我看到它看起来不错:

问题

使用 Autorest 创建 C# 客户端时出现意外结果。不知何故, TenantId 结构被删除并被 object 代替:

这是为什么?我应该怎么做才能自动生成 TenantId,就像客户端中的 PurchaseContract 一样?

详情

这里是版本信息。

我开始调查 Swashbuckle.AspNetCore.SwaggerGen to find out how my readonly struct was interpreted. It all happens in the class JsonSerializerDataContractResolver 的源代码,在方法 GetDataContractForType 中确定所提供类型的 DataContract

public DataContract GetDataContractForType(Type type)
{
    if (type.IsOneOf(typeof(object), typeof(JsonDocument), typeof(JsonElement)))
    {
        ...
    }

    if (PrimitiveTypesAndFormats.ContainsKey(type))
    {
        ...
    }

    if (type.IsEnum)
    {
        ...
    }

    if (IsSupportedDictionary(type, out Type keyType, out Type valueType))
    {
        ...
    }

    if (IsSupportedCollection(type, out Type itemType))
    {
        ...
    }

    return DataContract.ForObject(
        underlyingType: type,
        properties: GetDataPropertiesFor(type, out Type extensionDataType),
        extensionDataType: extensionDataType,
        jsonConverter: JsonConverterFunc);
}

我的习惯 struct TenantId 不符合这些条件中的任何一个,因此,它退回到被视为 object(最后一个陈述)。

然后我继续查看现有的 tests,看看 class 是如何使用的,看看我是否可以更改任何内容。令人惊讶的是,我发现了一个名为 GenerateSchema_SupportsOption_CustomTypeMappings 的测试(第 356 行),它显示了一种提供自定义映射的方法(请参阅该方法的第一条语句):

[Theory]
[InlineData(typeof(ComplexType), typeof(ComplexType), "string")]
[InlineData(typeof(GenericType<int, string>), typeof(GenericType<int, string>), "string")]
[InlineData(typeof(GenericType<,>), typeof(GenericType<int, int>), "string")]
public void GenerateSchema_SupportsOption_CustomTypeMappings(
            Type mappingType,
            Type type,
            string expectedSchemaType)
{
    var subject = Subject(configureGenerator: c => c.CustomTypeMappings.Add(mappingType, () => new OpenApiSchema { Type = "string" }));

    var schema = subject.GenerateSchema(type, new SchemaRepository());

    Assert.Equal(expectedSchemaType, schema.Type);
    Assert.Empty(schema.Properties);
}

就我而言,我希望将我的 TenantId 映射到 string。为此,我在 Function App 启动时编辑了 SwashBuckle 的配置:

private static void ConfigureSwashBuckle(IFunctionsHostBuilder functionsHostBuilder)
{
    functionsHostBuilder.AddSwashBuckle(Assembly.GetExecutingAssembly(), options =>
    {
        ...
        options.ConfigureSwaggerGen = (swaggerGenOptions) => swaggerGenOptions.MapType<TenantId>(() => new OpenApiSchema {Type = "string"});
    });
}

在这里,TenantId 现在在 Swagger 中被视为 string