Fix up packge for OpenAPI XML support (#611)

This commit is contained in:
Safia Abdalla 2024-10-22 09:29:53 -07:00 коммит произвёл GitHub
Родитель ab68617aa6
Коммит 776c606e78
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
29 изменённых файлов: 966 добавлений и 48 удалений

Просмотреть файл

@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "sample\Sample.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests", "test\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests.csproj", "{1541DE82-68F3-440A-9ABD-429EC986CB84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocFx.XmlComments", "src\DocFx.XmlComments.csproj", "{28570C7C-A9B1-4A6A-95B8-EA2153FCA4A3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -29,10 +27,6 @@ Global
{1541DE82-68F3-440A-9ABD-429EC986CB84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1541DE82-68F3-440A-9ABD-429EC986CB84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1541DE82-68F3-440A-9ABD-429EC986CB84}.Release|Any CPU.Build.0 = Release|Any CPU
{28570C7C-A9B1-4A6A-95B8-EA2153FCA4A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28570C7C-A9B1-4A6A-95B8-EA2153FCA4A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28570C7C-A9B1-4A6A-95B8-EA2153FCA4A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28570C7C-A9B1-4A6A-95B8-EA2153FCA4A3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -1,3 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace DocFx.XmlComments;

Просмотреть файл

@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

Просмотреть файл

@ -16,7 +16,7 @@ internal static class XmlCommentTransformer
private static XslCompiledTransform InitializeTransform()
{
var assembly = typeof(XmlCommentTransformer).Assembly;
var xsltFilePath = $"{assembly.GetName().Name}.Resources.XmlCommentTransform.xsl";
var xsltFilePath = $"{assembly.GetName().Name}.DocFx.XmlComments.Resources.XmlCommentTransform.xsl";
using var stream = assembly.GetManifestResourceStream(xsltFilePath);
using var reader = XmlReader.Create(stream);
var xsltSettings = new XsltSettings(true, true);

Просмотреть файл

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

Просмотреть файл

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

Просмотреть файл

@ -15,19 +15,24 @@
<NoWarn>RSEXPERIMENTAL002</NoWarn>
<LangVersion>preview</LangVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>source generator, openapi, xml</PackageTags>
<Description>Source generator to provide XML doc support for Microsoft.AspNetCore.OpenApi</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="All" IsImplicitlyDefined="true" Version="4.11.0-2.final" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="All" IsImplicitlyDefined="true" Version="4.11.0-2.final" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\DocFx.XmlComments.csproj" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
<ItemGroup>
<None Include=".\README.md" Pack="true" PackagePath="\"/>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include=".\build\Microsoft.AspNetCore.OpenApi.SourceGenerators.targets" Pack="true" PackagePath="build" Visible="false" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="DocFx.XmlComments\Resources\**" />
</ItemGroup>
</Project>

Просмотреть файл

@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated
{
public static IOpenApiAny ToOpenApiAny(string? example, Type type)
{
if (example is null || type is null)
if (string.IsNullOrEmpty(example) || type is null)
{
return new OpenApiNull();
}
@ -230,6 +230,17 @@ namespace Microsoft.AspNetCore.OpenApi.Generated
});
}
""",
AddOpenApiOverloadVariant.AddOpenApiConfigureOptions => """
public static IServiceCollection AddOpenApi(this IServiceCollection services, Action<OpenApiOptions> configureOptions)
{
return services.AddOpenApi("v1", options =>
{
configureOptions(options);
options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
options.AddOperationTransformer(new XmlCommentOperationTransformer());
});
}
""",
AddOpenApiOverloadVariant.AddOpenApiDocumentNameConfigureOptions => """
public static IServiceCollection AddOpenApi(this IServiceCollection services, string documentName, Action<OpenApiOptions> configureOptions)
{

Просмотреть файл

@ -87,12 +87,28 @@ public sealed partial class XmlCommentGenerator
{
var invocationExpression = (InvocationExpressionSyntax)context.Node;
var interceptableLocation = context.SemanticModel.GetInterceptableLocation(invocationExpression, cancellationToken);
return new(invocationExpression.ArgumentList.Arguments.Count switch
var argumentsCount = invocationExpression.ArgumentList.Arguments.Count;
if (argumentsCount == 0)
{
0 => AddOpenApiOverloadVariant.AddOpenApi,
1 => AddOpenApiOverloadVariant.AddOpenApiDocumentName,
2 => AddOpenApiOverloadVariant.AddOpenApiDocumentNameConfigureOptions,
_ => throw new InvalidOperationException("Invalid number of arguments for supported `AddOpenApi` overload."),
}, invocationExpression, interceptableLocation);
return new(AddOpenApiOverloadVariant.AddOpenApi, invocationExpression, interceptableLocation);
}
else if (argumentsCount == 2)
{
return new(AddOpenApiOverloadVariant.AddOpenApiDocumentNameConfigureOptions, invocationExpression, interceptableLocation);
}
else
{
// We need to disambiguate between the two overloads that take a string and a delegate
// AddOpenApi("v1") vs. AddOpenApi(options => { })
var argument = invocationExpression.ArgumentList.Arguments[0];
if (argument.Expression is LiteralExpressionSyntax)
{
return new(AddOpenApiOverloadVariant.AddOpenApiDocumentName, invocationExpression, interceptableLocation);
}
else
{
return new(AddOpenApiOverloadVariant.AddOpenApiConfigureOptions, invocationExpression, interceptableLocation);
}
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<PropertyGroup>
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
<InterceptorsPreviewNamespaces>
$(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsPreviewNamespaces>
</PropertyGroup>
</Project>

Просмотреть файл

@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.OpenApi.Generated
{
public static IOpenApiAny ToOpenApiAny(string? example, Type type)
{
if (example is null || type is null)
if (string.IsNullOrEmpty(example) || type is null)
{
return new OpenApiNull();
}

Просмотреть файл

@ -14,12 +14,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0-preview.rc.1.*" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0-preview.rc.2.*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../gen/Microsoft.AspNetCore.OpenApi.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="../src/DocFx.XmlComments.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
<ProjectReference Include="../gen/Microsoft.AspNetCore.OpenApi.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="../gen/Helpers/Polyfills.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="All" IsImplicitlyDefined="true" Version="4.11.0-2.final" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="All" IsImplicitlyDefined="true" Version="4.11.0-2.final" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,105 @@
using Microsoft.CodeAnalysis;
using Microsoft.OpenApi.Models;
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests;
public partial class AddOpenApiTests
{
[Fact]
public async Task CanInterceptAddOpenApiWithNoParameters()
{
var source = """
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapPost("", () => "Hello world!");
app.Run();
""";
var generator = new XmlCommentGenerator();
await SnapshotTestHelper.Verify(source, generator, out var compilation);
Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning));
}
[Fact]
public async Task CanInterceptAddOpenApiWithNameParameter()
{
var source = """
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi("v2");
var app = builder.Build();
app.MapPost("", () => "Hello world!");
app.Run();
""";
var generator = new XmlCommentGenerator();
await SnapshotTestHelper.Verify(source, generator, out var compilation);
Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning));
}
[Fact]
public async Task CanInterceptAddOpenApiWithConfigureOptionsParameter()
{
var source = """
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0;
});
var app = builder.Build();
app.MapPost("", () => "Hello world!");
app.Run();
""";
var generator = new XmlCommentGenerator();
await SnapshotTestHelper.Verify(source, generator, out var compilation);
Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning));
}
[Fact]
public async Task CanInterceptAddOpenApiWithNameAndConfigureOptionsParameter()
{
var source = """
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi("v2", options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0;
});
var app = builder.Build();
app.MapPost("", () => "Hello world!");
app.Run();
""";
var generator = new XmlCommentGenerator();
await SnapshotTestHelper.Verify(source, generator, out var compilation);
Assert.Empty(compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Warning));
}
}

Просмотреть файл

@ -28,7 +28,6 @@
<ItemGroup>
<ProjectReference Include="..\gen\Microsoft.AspNetCore.OpenApi.SourceGenerators.csproj" />
<ProjectReference Include="..\src\DocFx.XmlComments.csproj" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -0,0 +1,199 @@
//HintName: OpenApiXmlCommentSupport.generated.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
namespace System.Runtime.CompilerServices
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : System.Attribute
{
public InterceptsLocationAttribute(int version, string data)
{
}
}
}
namespace Microsoft.AspNetCore.OpenApi.Generated
{
using DocFx.XmlComments;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Any;
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class XmlCommentCache
{
private static Dictionary<(Type?, string?), string>? _cache;
public static Dictionary<(Type?, string?), string> Cache
{
get
{
if (_cache is null)
{
_cache = GenerateCacheEntries();
}
return _cache;
}
}
private static Dictionary<(Type?, string?), string> GenerateCacheEntries()
{
var _cache = new Dictionary<(Type?, string?), string>();
return _cache;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentOperationTransformer : IOpenApiOperationTransformer
{
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
{
var methodInfo = context.Description.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor
? controllerActionDescriptor.MethodInfo
: context.Description.ActionDescriptor.EndpointMetadata.OfType<MethodInfo>().SingleOrDefault();
if (methodInfo is null)
{
return Task.CompletedTask;
}
if (XmlCommentCache.Cache.TryGetValue((methodInfo.DeclaringType, methodInfo.Name), out var methodCommentString))
{
System.Diagnostics.Debugger.Break();
var methodComment = JsonSerializer.Deserialize<XmlComment>(methodCommentString);
if (methodComment is null)
{
return Task.CompletedTask;
}
operation.Summary = methodComment.Summary;
operation.Description = methodComment.Description;
foreach (var parameterComment in methodComment.Parameters)
{
var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name);
var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name);
if (operationParameter is not null)
{
operationParameter.Description = parameterComment.Description;
if (parameterInfo is not null)
{
operationParameter.Example = OpenApiExamplesHelper.ToOpenApiAny(parameterComment.Example, parameterInfo.ParameterType);
}
}
else
{
var requestBody = operation.RequestBody;
if (requestBody is not null)
{
requestBody.Description = parameterComment.Description;
}
}
}
if (methodComment.Responses is { Count: > 0} && operation.Responses is { Count: > 0 })
{
foreach (var response in operation.Responses)
{
var responseComment = methodComment.Responses.SingleOrDefault(xmlResponse => xmlResponse.Code == response.Key);
if (responseComment is not null)
{
response.Value.Description = responseComment.Description;
}
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
{
if (XmlCommentCache.Cache.TryGetValue((propertyInfo.DeclaringType, propertyInfo.Name), out var propertyCommentString))
{
var propertyComment = JsonSerializer.Deserialize<XmlComment>(propertyCommentString);
if (propertyComment is not null)
{
schema.Description = propertyComment.Returns ?? propertyComment.Summary;
if (propertyComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(propertyComment.Examples.FirstOrDefault(), propertyInfo.PropertyType);
}
}
}
}
if (XmlCommentCache.Cache.TryGetValue((context.JsonTypeInfo.Type, null), out var typeCommentString))
{
var typeComment = JsonSerializer.Deserialize<XmlComment>(typeCommentString);
if (typeComment is not null)
{
schema.Description = typeComment.Summary;
if (typeComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(typeComment.Examples.FirstOrDefault(), context.JsonTypeInfo.Type);
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class OpenApiExamplesHelper
{
public static IOpenApiAny ToOpenApiAny(string? example, Type type)
{
if (example is null || type is null)
{
return new OpenApiNull();
}
return Type.GetTypeCode(type) switch
{
TypeCode.String => new OpenApiString(example),
TypeCode.Boolean => new OpenApiBoolean(bool.Parse(example)),
TypeCode.Int32 => new OpenApiInteger(int.Parse(example)),
TypeCode.Int64 => new OpenApiLong(long.Parse(example)),
TypeCode.Double => new OpenApiDouble(double.Parse(example)),
TypeCode.Single => new OpenApiFloat(float.Parse(example)),
TypeCode.DateTime => new OpenApiDateTime(DateTime.Parse(example)),
_ => new OpenApiNull()
};
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class GeneratedServiceCollectionExtensions
{
[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "WEYG7v0MUfj4KhOfDMNMrZUAAABQcm9ncmFtLmNz")]
public static IServiceCollection AddOpenApi(this IServiceCollection services, string documentName)
{
return services.AddOpenApi(documentName, options =>
{
options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
options.AddOperationTransformer(new XmlCommentOperationTransformer());
});
}
}
}

Просмотреть файл

@ -0,0 +1,201 @@
//HintName: OpenApiXmlCommentSupport.generated.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
namespace System.Runtime.CompilerServices
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : System.Attribute
{
public InterceptsLocationAttribute(int version, string data)
{
}
}
}
namespace Microsoft.AspNetCore.OpenApi.Generated
{
using DocFx.XmlComments;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Any;
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class XmlCommentCache
{
private static Dictionary<(Type?, string?), string>? _cache;
public static Dictionary<(Type?, string?), string> Cache
{
get
{
if (_cache is null)
{
_cache = GenerateCacheEntries();
}
return _cache;
}
}
private static Dictionary<(Type?, string?), string> GenerateCacheEntries()
{
var _cache = new Dictionary<(Type?, string?), string>();
return _cache;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentOperationTransformer : IOpenApiOperationTransformer
{
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
{
var methodInfo = context.Description.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor
? controllerActionDescriptor.MethodInfo
: context.Description.ActionDescriptor.EndpointMetadata.OfType<MethodInfo>().SingleOrDefault();
if (methodInfo is null)
{
return Task.CompletedTask;
}
if (XmlCommentCache.Cache.TryGetValue((methodInfo.DeclaringType, methodInfo.Name), out var methodCommentString))
{
System.Diagnostics.Debugger.Break();
var methodComment = JsonSerializer.Deserialize<XmlComment>(methodCommentString);
if (methodComment is null)
{
return Task.CompletedTask;
}
operation.Summary = methodComment.Summary;
operation.Description = methodComment.Description;
foreach (var parameterComment in methodComment.Parameters)
{
var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name);
var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name);
if (operationParameter is not null)
{
operationParameter.Description = parameterComment.Description;
if (parameterInfo is not null)
{
operationParameter.Example = OpenApiExamplesHelper.ToOpenApiAny(parameterComment.Example, parameterInfo.ParameterType);
}
}
else
{
var requestBody = operation.RequestBody;
if (requestBody is not null)
{
requestBody.Description = parameterComment.Description;
}
}
}
if (methodComment.Responses is { Count: > 0} && operation.Responses is { Count: > 0 })
{
foreach (var response in operation.Responses)
{
var responseComment = methodComment.Responses.SingleOrDefault(xmlResponse => xmlResponse.Code == response.Key);
if (responseComment is not null)
{
response.Value.Description = responseComment.Description;
}
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
{
if (XmlCommentCache.Cache.TryGetValue((propertyInfo.DeclaringType, propertyInfo.Name), out var propertyCommentString))
{
var propertyComment = JsonSerializer.Deserialize<XmlComment>(propertyCommentString);
if (propertyComment is not null)
{
schema.Description = propertyComment.Returns ?? propertyComment.Summary;
if (propertyComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(propertyComment.Examples.FirstOrDefault(), propertyInfo.PropertyType);
}
}
}
}
if (XmlCommentCache.Cache.TryGetValue((context.JsonTypeInfo.Type, null), out var typeCommentString))
{
var typeComment = JsonSerializer.Deserialize<XmlComment>(typeCommentString);
if (typeComment is not null)
{
schema.Description = typeComment.Summary;
if (typeComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(typeComment.Examples.FirstOrDefault(), context.JsonTypeInfo.Type);
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class OpenApiExamplesHelper
{
public static IOpenApiAny ToOpenApiAny(string? example, Type type)
{
if (example is null || type is null)
{
return new OpenApiNull();
}
return Type.GetTypeCode(type) switch
{
TypeCode.String => new OpenApiString(example),
TypeCode.Boolean => new OpenApiBoolean(bool.Parse(example)),
TypeCode.Int32 => new OpenApiInteger(int.Parse(example)),
TypeCode.Int64 => new OpenApiLong(long.Parse(example)),
TypeCode.Double => new OpenApiDouble(double.Parse(example)),
TypeCode.Single => new OpenApiFloat(float.Parse(example)),
TypeCode.DateTime => new OpenApiDateTime(DateTime.Parse(example)),
_ => new OpenApiNull()
};
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class GeneratedServiceCollectionExtensions
{
[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "o5sPKTobw7zSg5h72CUcUpUAAABQcm9ncmFtLmNz")]
public static IServiceCollection AddOpenApi(this IServiceCollection services, string documentName, Action<OpenApiOptions> configureOptions)
{
// This overload is not intercepted.
return OpenApiServiceCollectionExtensions.AddOpenApi(services, documentName, options =>
{
configureOptions(options);
options.AddSchemaTransformer(new XmlCommentTransformer());
options.AddOperationTransformer(new XmlCommentOperationTransformer());
});
}
}
}

Просмотреть файл

@ -0,0 +1,199 @@
//HintName: OpenApiXmlCommentSupport.generated.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
namespace System.Runtime.CompilerServices
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : System.Attribute
{
public InterceptsLocationAttribute(int version, string data)
{
}
}
}
namespace Microsoft.AspNetCore.OpenApi.Generated
{
using DocFx.XmlComments;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Any;
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class XmlCommentCache
{
private static Dictionary<(Type?, string?), string>? _cache;
public static Dictionary<(Type?, string?), string> Cache
{
get
{
if (_cache is null)
{
_cache = GenerateCacheEntries();
}
return _cache;
}
}
private static Dictionary<(Type?, string?), string> GenerateCacheEntries()
{
var _cache = new Dictionary<(Type?, string?), string>();
return _cache;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentOperationTransformer : IOpenApiOperationTransformer
{
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
{
var methodInfo = context.Description.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor
? controllerActionDescriptor.MethodInfo
: context.Description.ActionDescriptor.EndpointMetadata.OfType<MethodInfo>().SingleOrDefault();
if (methodInfo is null)
{
return Task.CompletedTask;
}
if (XmlCommentCache.Cache.TryGetValue((methodInfo.DeclaringType, methodInfo.Name), out var methodCommentString))
{
System.Diagnostics.Debugger.Break();
var methodComment = JsonSerializer.Deserialize<XmlComment>(methodCommentString);
if (methodComment is null)
{
return Task.CompletedTask;
}
operation.Summary = methodComment.Summary;
operation.Description = methodComment.Description;
foreach (var parameterComment in methodComment.Parameters)
{
var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name);
var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name);
if (operationParameter is not null)
{
operationParameter.Description = parameterComment.Description;
if (parameterInfo is not null)
{
operationParameter.Example = OpenApiExamplesHelper.ToOpenApiAny(parameterComment.Example, parameterInfo.ParameterType);
}
}
else
{
var requestBody = operation.RequestBody;
if (requestBody is not null)
{
requestBody.Description = parameterComment.Description;
}
}
}
if (methodComment.Responses is { Count: > 0} && operation.Responses is { Count: > 0 })
{
foreach (var response in operation.Responses)
{
var responseComment = methodComment.Responses.SingleOrDefault(xmlResponse => xmlResponse.Code == response.Key);
if (responseComment is not null)
{
response.Value.Description = responseComment.Description;
}
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
{
if (XmlCommentCache.Cache.TryGetValue((propertyInfo.DeclaringType, propertyInfo.Name), out var propertyCommentString))
{
var propertyComment = JsonSerializer.Deserialize<XmlComment>(propertyCommentString);
if (propertyComment is not null)
{
schema.Description = propertyComment.Returns ?? propertyComment.Summary;
if (propertyComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(propertyComment.Examples.FirstOrDefault(), propertyInfo.PropertyType);
}
}
}
}
if (XmlCommentCache.Cache.TryGetValue((context.JsonTypeInfo.Type, null), out var typeCommentString))
{
var typeComment = JsonSerializer.Deserialize<XmlComment>(typeCommentString);
if (typeComment is not null)
{
schema.Description = typeComment.Summary;
if (typeComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(typeComment.Examples.FirstOrDefault(), context.JsonTypeInfo.Type);
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class OpenApiExamplesHelper
{
public static IOpenApiAny ToOpenApiAny(string? example, Type type)
{
if (example is null || type is null)
{
return new OpenApiNull();
}
return Type.GetTypeCode(type) switch
{
TypeCode.String => new OpenApiString(example),
TypeCode.Boolean => new OpenApiBoolean(bool.Parse(example)),
TypeCode.Int32 => new OpenApiInteger(int.Parse(example)),
TypeCode.Int64 => new OpenApiLong(long.Parse(example)),
TypeCode.Double => new OpenApiDouble(double.Parse(example)),
TypeCode.Single => new OpenApiFloat(float.Parse(example)),
TypeCode.DateTime => new OpenApiDateTime(DateTime.Parse(example)),
_ => new OpenApiNull()
};
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class GeneratedServiceCollectionExtensions
{
[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "Oxa8kEsc+P8JWEeyXj4l05UAAABQcm9ncmFtLmNz")]
public static IServiceCollection AddOpenApi(this IServiceCollection services, string documentName)
{
return services.AddOpenApi(documentName, options =>
{
options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
options.AddOperationTransformer(new XmlCommentOperationTransformer());
});
}
}
}

Просмотреть файл

@ -0,0 +1,199 @@
//HintName: OpenApiXmlCommentSupport.generated.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
namespace System.Runtime.CompilerServices
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : System.Attribute
{
public InterceptsLocationAttribute(int version, string data)
{
}
}
}
namespace Microsoft.AspNetCore.OpenApi.Generated
{
using DocFx.XmlComments;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Any;
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class XmlCommentCache
{
private static Dictionary<(Type?, string?), string>? _cache;
public static Dictionary<(Type?, string?), string> Cache
{
get
{
if (_cache is null)
{
_cache = GenerateCacheEntries();
}
return _cache;
}
}
private static Dictionary<(Type?, string?), string> GenerateCacheEntries()
{
var _cache = new Dictionary<(Type?, string?), string>();
return _cache;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentOperationTransformer : IOpenApiOperationTransformer
{
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
{
var methodInfo = context.Description.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor
? controllerActionDescriptor.MethodInfo
: context.Description.ActionDescriptor.EndpointMetadata.OfType<MethodInfo>().SingleOrDefault();
if (methodInfo is null)
{
return Task.CompletedTask;
}
if (XmlCommentCache.Cache.TryGetValue((methodInfo.DeclaringType, methodInfo.Name), out var methodCommentString))
{
System.Diagnostics.Debugger.Break();
var methodComment = JsonSerializer.Deserialize<XmlComment>(methodCommentString);
if (methodComment is null)
{
return Task.CompletedTask;
}
operation.Summary = methodComment.Summary;
operation.Description = methodComment.Description;
foreach (var parameterComment in methodComment.Parameters)
{
var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name);
var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name);
if (operationParameter is not null)
{
operationParameter.Description = parameterComment.Description;
if (parameterInfo is not null)
{
operationParameter.Example = OpenApiExamplesHelper.ToOpenApiAny(parameterComment.Example, parameterInfo.ParameterType);
}
}
else
{
var requestBody = operation.RequestBody;
if (requestBody is not null)
{
requestBody.Description = parameterComment.Description;
}
}
}
if (methodComment.Responses is { Count: > 0} && operation.Responses is { Count: > 0 })
{
foreach (var response in operation.Responses)
{
var responseComment = methodComment.Responses.SingleOrDefault(xmlResponse => xmlResponse.Code == response.Key);
if (responseComment is not null)
{
response.Value.Description = responseComment.Description;
}
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
{
if (XmlCommentCache.Cache.TryGetValue((propertyInfo.DeclaringType, propertyInfo.Name), out var propertyCommentString))
{
var propertyComment = JsonSerializer.Deserialize<XmlComment>(propertyCommentString);
if (propertyComment is not null)
{
schema.Description = propertyComment.Returns ?? propertyComment.Summary;
if (propertyComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(propertyComment.Examples.FirstOrDefault(), propertyInfo.PropertyType);
}
}
}
}
if (XmlCommentCache.Cache.TryGetValue((context.JsonTypeInfo.Type, null), out var typeCommentString))
{
var typeComment = JsonSerializer.Deserialize<XmlComment>(typeCommentString);
if (typeComment is not null)
{
schema.Description = typeComment.Summary;
if (typeComment.Examples is { Count: > 0 })
{
schema.Example = OpenApiExamplesHelper.ToOpenApiAny(typeComment.Examples.FirstOrDefault(), context.JsonTypeInfo.Type);
}
}
}
return Task.CompletedTask;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class OpenApiExamplesHelper
{
public static IOpenApiAny ToOpenApiAny(string? example, Type type)
{
if (example is null || type is null)
{
return new OpenApiNull();
}
return Type.GetTypeCode(type) switch
{
TypeCode.String => new OpenApiString(example),
TypeCode.Boolean => new OpenApiBoolean(bool.Parse(example)),
TypeCode.Int32 => new OpenApiInteger(int.Parse(example)),
TypeCode.Int64 => new OpenApiLong(long.Parse(example)),
TypeCode.Double => new OpenApiDouble(double.Parse(example)),
TypeCode.Single => new OpenApiFloat(float.Parse(example)),
TypeCode.DateTime => new OpenApiDateTime(DateTime.Parse(example)),
_ => new OpenApiNull()
};
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class GeneratedServiceCollectionExtensions
{
[global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "TLaexAhvQY+MEDWz7mc7e5UAAABQcm9ncmFtLmNz")]
public static IServiceCollection AddOpenApi(this IServiceCollection services)
{
return services.AddOpenApi("v1", options =>
{
options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
options.AddOperationTransformer(new XmlCommentOperationTransformer());
});
}
}
}