зеркало из https://github.com/dotnet/aspnetcore.git
Add OpenAPI operation and schema transformer interfaces (#56395)
* Add operation and schema transformer interfaces - Add `IOpenApiOperationTransformer`. - Add `IOpenApiSchemaTransformer`. - Rename `Use*Transfomer()` methods to `Add*Transformer()`. Resolves #56022. * Add operation and schema transform examples Add examples for DI-activated operation and schema transformers. * Fix rebase Fix mistake during rebase. * Run operation transforms before documents Run operation transformers before any document transformers. * Move transformer Add type-based transformer for operations to its own class. * Revert submodule Revert accidental change to submodule during rebase. * Apply review feedback - Remove collection expression. - Fix two typos. - Add parameter to benchmark.
This commit is contained in:
Родитель
13d056acf6
Коммит
2d47d49abc
|
@ -1788,7 +1788,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotReferencedInWasmCodePack
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components.WasmRemoteAuthentication", "src\Components\test\testassets\Components.WasmRemoteAuthentication\Components.WasmRemoteAuthentication.csproj", "{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "src\OpenApi\sample\Sample.csproj", "{6DEC24A8-A166-432F-8E3B-58FFCDA92F52}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "src\OpenApi\sample\Sample.csproj", "{6DEC24A8-A166-432F-8E3B-58FFCDA92F52}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hybrid", "Hybrid", "{2D64CA23-6E81-488E-A7D3-9BDF87240098}"
|
||||
EndProject
|
||||
|
@ -1802,7 +1802,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Cachin
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{9DC6B242-457B-4767-A84B-C3D23B76C642}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.OpenApi.Microbenchmarks", "src\OpenApi\perf\Microbenchmarks\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj", "{D53F0EF7-0CDC-49B4-AA2D-229901B0A734}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OpenApi.Microbenchmarks", "src\OpenApi\perf\Microbenchmarks\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj", "{D53F0EF7-0CDC-49B4-AA2D-229901B0A734}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyManagementSimulator", "src\DataProtection\samples\KeyManagementSimulator\KeyManagementSimulator.csproj", "{5B5F86CC-3598-463C-9F9B-F78FBB6642F4}"
|
||||
EndProject
|
||||
|
|
|
@ -2,15 +2,19 @@
|
|||
"solution": {
|
||||
"path": "..\\..\\AspNetCore.sln",
|
||||
"projects": [
|
||||
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
|
||||
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
|
||||
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
|
||||
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
|
||||
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
|
||||
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
|
||||
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
|
||||
"src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj",
|
||||
"src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj",
|
||||
"src\\OpenApi\\sample\\Sample.csproj",
|
||||
"src\\OpenApi\\src\\Microsoft.AspNetCore.OpenApi.csproj",
|
||||
"src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.Tests.csproj",
|
||||
"src\\OpenApi\\sample\\Sample.csproj",
|
||||
"src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj"
|
||||
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,16 +21,27 @@ public class TransformersBenchmark : OpenApiDocumentServiceTestBase
|
|||
public int TransformerCount { get; set; }
|
||||
|
||||
private readonly IEndpointRouteBuilder _builder = CreateBuilder();
|
||||
private readonly OpenApiOptions _options = new OpenApiOptions();
|
||||
private readonly OpenApiOptions _options = new();
|
||||
private OpenApiDocumentService _documentService;
|
||||
|
||||
[GlobalSetup(Target = nameof(ActivatedOperationTransformer))]
|
||||
public void ActivatedOperationTransformer_Setup()
|
||||
{
|
||||
_builder.MapGet("/", () => { });
|
||||
for (var i = 0; i <= TransformerCount; i++)
|
||||
{
|
||||
_options.AddOperationTransformer<OperationTransformer>();
|
||||
}
|
||||
_documentService = CreateDocumentService(_builder, _options);
|
||||
}
|
||||
|
||||
[GlobalSetup(Target = nameof(OperationTransformerAsDelegate))]
|
||||
public void OperationTransformerAsDelegate_Setup()
|
||||
{
|
||||
_builder.MapGet("/", () => { });
|
||||
for (var i = 0; i <= TransformerCount; i++)
|
||||
{
|
||||
_options.UseOperationTransformer((operation, context, token) =>
|
||||
_options.AddOperationTransformer((operation, context, token) =>
|
||||
{
|
||||
operation.Description = "New Description";
|
||||
return Task.CompletedTask;
|
||||
|
@ -45,7 +56,7 @@ public class TransformersBenchmark : OpenApiDocumentServiceTestBase
|
|||
_builder.MapGet("/", () => { });
|
||||
for (var i = 0; i <= TransformerCount; i++)
|
||||
{
|
||||
_options.UseTransformer<ActivatedTransformer>();
|
||||
_options.AddDocumentTransformer<DocumentTransformer>();
|
||||
}
|
||||
_documentService = CreateDocumentService(_builder, _options);
|
||||
}
|
||||
|
@ -56,7 +67,7 @@ public class TransformersBenchmark : OpenApiDocumentServiceTestBase
|
|||
_builder.MapGet("/", () => { });
|
||||
for (var i = 0; i <= TransformerCount; i++)
|
||||
{
|
||||
_options.UseTransformer((document, context, token) =>
|
||||
_options.AddDocumentTransformer((document, context, token) =>
|
||||
{
|
||||
document.Info.Description = "New Description";
|
||||
return Task.CompletedTask;
|
||||
|
@ -65,13 +76,24 @@ public class TransformersBenchmark : OpenApiDocumentServiceTestBase
|
|||
_documentService = CreateDocumentService(_builder, _options);
|
||||
}
|
||||
|
||||
[GlobalSetup(Target = nameof(SchemaTransformer))]
|
||||
[GlobalSetup(Target = nameof(ActivatedSchemaTransformer))]
|
||||
public void ActivatedSchemaTransformer_Setup()
|
||||
{
|
||||
_builder.MapPost("/", (Todo todo) => todo);
|
||||
for (var i = 0; i <= TransformerCount; i++)
|
||||
{
|
||||
_options.AddSchemaTransformer<SchemaTransformer>();
|
||||
}
|
||||
_documentService = CreateDocumentService(_builder, _options);
|
||||
}
|
||||
|
||||
[GlobalSetup(Target = nameof(SchemaTransformerAsDelegate))]
|
||||
public void SchemaTransformer_Setup()
|
||||
{
|
||||
_builder.MapPost("/", (Todo todo) => todo);
|
||||
for (var i = 0; i <= TransformerCount; i++)
|
||||
{
|
||||
_options.UseSchemaTransformer((schema, context, token) =>
|
||||
_options.AddSchemaTransformer((schema, context, token) =>
|
||||
{
|
||||
if (context.Type == typeof(Todo) && context.ParameterDescription != null)
|
||||
{
|
||||
|
@ -87,6 +109,12 @@ public class TransformersBenchmark : OpenApiDocumentServiceTestBase
|
|||
_documentService = CreateDocumentService(_builder, _options);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ActivatedOperationTransformer()
|
||||
{
|
||||
await _documentService.GetOpenApiDocumentAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task OperationTransformerAsDelegate()
|
||||
{
|
||||
|
@ -106,12 +134,18 @@ public class TransformersBenchmark : OpenApiDocumentServiceTestBase
|
|||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task SchemaTransformer()
|
||||
public async Task ActivatedSchemaTransformer()
|
||||
{
|
||||
await _documentService.GetOpenApiDocumentAsync();
|
||||
}
|
||||
|
||||
private class ActivatedTransformer : IOpenApiDocumentTransformer
|
||||
[Benchmark]
|
||||
public async Task SchemaTransformerAsDelegate()
|
||||
{
|
||||
await _documentService.GetOpenApiDocumentAsync();
|
||||
}
|
||||
|
||||
private class DocumentTransformer : IOpenApiDocumentTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -119,4 +153,29 @@ public class TransformersBenchmark : OpenApiDocumentServiceTestBase
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class OperationTransformer : IOpenApiOperationTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
operation.Description = "Operation Description";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class SchemaTransformer : IOpenApiSchemaTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (context.Type == typeof(Todo) && context.ParameterDescription != null)
|
||||
{
|
||||
schema.Extensions["x-my-extension"] = new OpenApiString(context.ParameterDescription.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
schema.Extensions["x-my-extension"] = new OpenApiString("response");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
|
@ -19,11 +18,13 @@ builder.Services.AddAuthentication().AddJwtBearer();
|
|||
builder.Services.AddOpenApi("v1", options =>
|
||||
{
|
||||
options.AddHeader("X-Version", "1.0");
|
||||
options.UseTransformer<BearerSecuritySchemeTransformer>();
|
||||
options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
|
||||
});
|
||||
builder.Services.AddOpenApi("v2", options => {
|
||||
options.UseTransformer(new AddContactTransformer());
|
||||
options.UseTransformer((document, context, token) => {
|
||||
options.AddSchemaTransformer<AddExternalDocsTransformer>();
|
||||
options.AddOperationTransformer<AddExternalDocsTransformer>();
|
||||
options.AddDocumentTransformer(new AddContactTransformer());
|
||||
options.AddDocumentTransformer((document, context, token) => {
|
||||
document.Info.License = new OpenApiLicense { Name = "MIT" };
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
@ -82,7 +83,8 @@ v1.MapGet("/todos/{id}", (int id) => new TodoWithDueDate(1, "Test todo", false,
|
|||
v2.MapGet("/users", () => new [] { "alice", "bob" })
|
||||
.WithTags("users");
|
||||
|
||||
v2.MapPost("/users", () => Results.Created("/users/1", new { Id = 1, Name = "Test user" }));
|
||||
v2.MapPost("/users", () => Results.Created("/users/1", new { Id = 1, Name = "Test user" }))
|
||||
.WithName("CreateUser");
|
||||
|
||||
responses.MapGet("/200-add-xml", () => new TodoWithDueDate(1, "Test todo", false, DateTime.Now.AddDays(1), DateTime.Now))
|
||||
.Produces<Todo>(additionalContentTypes: "text/xml");
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Sample.Transformers;
|
||||
|
||||
public sealed class AddExternalDocsTransformer(IConfiguration configuration) : IOpenApiOperationTransformer, IOpenApiSchemaTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (operation.OperationId is { Length: > 0 } id &&
|
||||
Uri.TryCreate(configuration["DocumentationBaseUrl"], UriKind.Absolute, out var baseUri))
|
||||
{
|
||||
var url = new Uri(baseUri, $"/api/docs/operations/{Uri.EscapeDataString(id)}");
|
||||
|
||||
operation.ExternalDocs = new OpenApiExternalDocs
|
||||
{
|
||||
Description = "Documentation for this OpenAPI endpoint",
|
||||
Url = url
|
||||
};
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Uri.TryCreate(configuration["DocumentationBaseUrl"], UriKind.Absolute, out var baseUri))
|
||||
{
|
||||
var url = new Uri(baseUri, $"/api/docs/schemas/{Uri.EscapeDataString(schema.Type)}");
|
||||
|
||||
schema.ExternalDocs = new OpenApiExternalDocs
|
||||
{
|
||||
Description = "Documentation for this OpenAPI schema",
|
||||
Url = url
|
||||
};
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Extensions;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Sample.Transformers;
|
||||
|
||||
|
@ -12,7 +12,7 @@ public static class OperationTransformers
|
|||
{
|
||||
public static OpenApiOptions AddHeader(this OpenApiOptions options, string headerName, string defaultValue)
|
||||
{
|
||||
return options.UseOperationTransformer((operation, context, cancellationToken) =>
|
||||
return options.AddOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
var schema = OpenApiTypeMapper.MapTypeToOpenApiPrimitiveType(typeof(string));
|
||||
schema.Default = new OpenApiString(defaultValue);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"DocumentationBaseUrl": "https://example.com",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
#nullable enable
|
||||
Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions
|
||||
Microsoft.AspNetCore.OpenApi.IOpenApiDocumentTransformer.TransformAsync(Microsoft.OpenApi.Models.OpenApiDocument! document, Microsoft.AspNetCore.OpenApi.OpenApiDocumentTransformerContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
|
||||
Microsoft.AspNetCore.OpenApi.IOpenApiOperationTransformer
|
||||
Microsoft.AspNetCore.OpenApi.IOpenApiOperationTransformer.TransformAsync(Microsoft.OpenApi.Models.OpenApiOperation! operation, Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
|
||||
Microsoft.AspNetCore.OpenApi.IOpenApiSchemaTransformer
|
||||
Microsoft.AspNetCore.OpenApi.IOpenApiSchemaTransformer.TransformAsync(Microsoft.OpenApi.Models.OpenApiSchema! schema, Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.Description.get -> Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.Description.init -> void
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddOperationTransformer(Microsoft.AspNetCore.OpenApi.IOpenApiOperationTransformer! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddOperationTransformer<TTransformerType>() -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddSchemaTransformer(Microsoft.AspNetCore.OpenApi.IOpenApiSchemaTransformer! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddSchemaTransformer<TTransformerType>() -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.CreateSchemaReferenceId.get -> System.Func<System.Text.Json.Serialization.Metadata.JsonTypeInfo!, string?>!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.CreateSchemaReferenceId.set -> void
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.DocumentName.get -> string!
|
||||
|
@ -12,11 +20,11 @@ Microsoft.AspNetCore.OpenApi.OpenApiOptions.OpenApiVersion.get -> Microsoft.Open
|
|||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.OpenApiVersion.set -> void
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.ShouldInclude.get -> System.Func<Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription!, bool>!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.ShouldInclude.set -> void
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.UseOperationTransformer(System.Func<Microsoft.OpenApi.Models.OpenApiOperation!, Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext!, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.UseSchemaTransformer(System.Func<Microsoft.OpenApi.Models.OpenApiSchema!, Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext!, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.UseTransformer(Microsoft.AspNetCore.OpenApi.IOpenApiDocumentTransformer! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.UseTransformer(System.Func<Microsoft.OpenApi.Models.OpenApiDocument!, Microsoft.AspNetCore.OpenApi.OpenApiDocumentTransformerContext!, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.UseTransformer<TTransformerType>() -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddOperationTransformer(System.Func<Microsoft.OpenApi.Models.OpenApiOperation!, Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext!, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddSchemaTransformer(System.Func<Microsoft.OpenApi.Models.OpenApiSchema!, Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext!, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddDocumentTransformer(Microsoft.AspNetCore.OpenApi.IOpenApiDocumentTransformer! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddDocumentTransformer(System.Func<Microsoft.OpenApi.Models.OpenApiDocument!, Microsoft.AspNetCore.OpenApi.OpenApiDocumentTransformerContext!, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! transformer) -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiOptions.AddDocumentTransformer<TTransformerType>() -> Microsoft.AspNetCore.OpenApi.OpenApiOptions!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.ApplicationServices.get -> System.IServiceProvider!
|
||||
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.ApplicationServices.init -> void
|
||||
|
|
|
@ -36,9 +36,9 @@ internal sealed class OpenApiDocumentService(
|
|||
{
|
||||
private readonly OpenApiOptions _options = optionsMonitor.Get(documentName);
|
||||
private readonly OpenApiSchemaService _componentService = serviceProvider.GetRequiredKeyedService<OpenApiSchemaService>(documentName);
|
||||
private readonly IOpenApiDocumentTransformer _schemaReferenceTransformer = new OpenApiSchemaReferenceTransformer();
|
||||
private readonly OpenApiSchemaReferenceTransformer _schemaReferenceTransformer = new();
|
||||
|
||||
private static readonly OpenApiEncoding _defaultFormEncoding = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = true };
|
||||
private static readonly OpenApiEncoding _defaultFormEncoding = new() { Style = ParameterStyle.Form, Explode = true };
|
||||
|
||||
/// <summary>
|
||||
/// Cache of <see cref="OpenApiOperationTransformerContext"/> instances keyed by the
|
||||
|
@ -47,7 +47,7 @@ internal sealed class OpenApiDocumentService(
|
|||
/// operations, API descriptions, and their respective transformer contexts.
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, OpenApiOperationTransformerContext> _operationTransformerContextCache = new();
|
||||
private static readonly ApiResponseType _defaultApiResponseType = new ApiResponseType { StatusCode = StatusCodes.Status200OK };
|
||||
private static readonly ApiResponseType _defaultApiResponseType = new() { StatusCode = StatusCodes.Status200OK };
|
||||
|
||||
internal bool TryGetCachedOperationTransformerContext(string descriptionId, [NotNullWhen(true)] out OpenApiOperationTransformerContext? context)
|
||||
=> _operationTransformerContextCache.TryGetValue(descriptionId, out context);
|
||||
|
@ -86,6 +86,41 @@ internal sealed class OpenApiDocumentService(
|
|||
await _schemaReferenceTransformer.TransformAsync(document, documentTransformerContext, cancellationToken);
|
||||
}
|
||||
|
||||
internal async Task ForEachOperationAsync(
|
||||
OpenApiDocument document,
|
||||
Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> callback,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var pathItem in document.Paths.Values)
|
||||
{
|
||||
for (var i = 0; i < OpenApiConstants.OperationTypes.Length; i++)
|
||||
{
|
||||
var operationType = OpenApiConstants.OperationTypes[i];
|
||||
if (!pathItem.Operations.TryGetValue(operationType, out var operation))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operation.Extensions.TryGetValue(OpenApiConstants.DescriptionId, out var descriptionIdExtension) &&
|
||||
descriptionIdExtension is ScrubbedOpenApiAny { Value: string descriptionId } &&
|
||||
TryGetCachedOperationTransformerContext(descriptionId, out var operationContext))
|
||||
{
|
||||
await callback(operation, operationContext, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the cached operation transformer context was not found, throw an exception.
|
||||
// This can occur if the `x-aspnetcore-id` extension attribute was removed by the
|
||||
// user in another operation transformer or if the lookup for operation transformer
|
||||
// context resulted in a cache miss. As an alternative here, we could just to implement
|
||||
// the "slow-path" and look up the ApiDescription associated with the OpenApiOperation
|
||||
// using the OperationType and given path, but we'll avoid this for now.
|
||||
throw new InvalidOperationException("Cached operation transformer context not found. Please ensure that the operation contains the `x-aspnetcore-id` extension attribute.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Internal for testing.
|
||||
internal OpenApiInfo GetOpenApiInfo()
|
||||
{
|
||||
|
@ -137,13 +172,23 @@ internal sealed class OpenApiDocumentService(
|
|||
{
|
||||
var operation = await GetOperationAsync(description, capturedTags, cancellationToken);
|
||||
operation.Extensions.Add(OpenApiConstants.DescriptionId, new ScrubbedOpenApiAny(description.ActionDescriptor.Id));
|
||||
_operationTransformerContextCache.TryAdd(description.ActionDescriptor.Id, new OpenApiOperationTransformerContext
|
||||
|
||||
var operationContext = new OpenApiOperationTransformerContext
|
||||
{
|
||||
DocumentName = documentName,
|
||||
Description = description,
|
||||
ApplicationServices = serviceProvider,
|
||||
});
|
||||
};
|
||||
|
||||
_operationTransformerContextCache.TryAdd(description.ActionDescriptor.Id, operationContext);
|
||||
operations[description.GetOperationType()] = operation;
|
||||
|
||||
// Use index-based for loop to avoid allocating an enumerator with a foreach.
|
||||
for (var i = 0; i < _options.OperationTransformers.Count; i++)
|
||||
{
|
||||
var transformer = _options.OperationTransformers[i];
|
||||
await transformer.TransformAsync(operation, operationContext, cancellationToken);
|
||||
}
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ namespace Microsoft.AspNetCore.OpenApi;
|
|||
public sealed class OpenApiOptions
|
||||
{
|
||||
internal readonly List<IOpenApiDocumentTransformer> DocumentTransformers = [];
|
||||
internal readonly List<Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task>> SchemaTransformers = [];
|
||||
internal readonly List<IOpenApiOperationTransformer> OperationTransformers = [];
|
||||
internal readonly List<IOpenApiSchemaTransformer> SchemaTransformers = [];
|
||||
|
||||
/// <summary>
|
||||
/// A default implementation for creating a schema reference ID for a given <see cref="JsonTypeInfo"/>.
|
||||
|
@ -62,7 +63,7 @@ public sealed class OpenApiOptions
|
|||
/// </summary>
|
||||
/// <typeparam name="TTransformerType">The type of the <see cref="IOpenApiDocumentTransformer"/> to instantiate.</typeparam>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions UseTransformer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TTransformerType>()
|
||||
public OpenApiOptions AddDocumentTransformer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TTransformerType>()
|
||||
where TTransformerType : IOpenApiDocumentTransformer
|
||||
{
|
||||
DocumentTransformers.Add(new TypeBasedOpenApiDocumentTransformer(typeof(TTransformerType)));
|
||||
|
@ -74,9 +75,9 @@ public sealed class OpenApiOptions
|
|||
/// </summary>
|
||||
/// <param name="transformer">The <see cref="IOpenApiDocumentTransformer"/> instance to use.</param>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions UseTransformer(IOpenApiDocumentTransformer transformer)
|
||||
public OpenApiOptions AddDocumentTransformer(IOpenApiDocumentTransformer transformer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(transformer, nameof(transformer));
|
||||
ArgumentNullException.ThrowIfNull(transformer);
|
||||
|
||||
DocumentTransformers.Add(transformer);
|
||||
return this;
|
||||
|
@ -87,24 +88,74 @@ public sealed class OpenApiOptions
|
|||
/// </summary>
|
||||
/// <param name="transformer">The delegate representing the document transformer.</param>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions UseTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
|
||||
public OpenApiOptions AddDocumentTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(transformer, nameof(transformer));
|
||||
ArgumentNullException.ThrowIfNull(transformer);
|
||||
|
||||
DocumentTransformers.Add(new DelegateOpenApiDocumentTransformer(transformer));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new operation transformer on the current <see cref="OpenApiOptions"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTransformerType">The type of the <see cref="IOpenApiOperationTransformer"/> to instantiate.</typeparam>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions AddOperationTransformer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TTransformerType>()
|
||||
where TTransformerType : IOpenApiOperationTransformer
|
||||
{
|
||||
OperationTransformers.Add(new TypeBasedOpenApiOperationTransformer(typeof(TTransformerType)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a given instance of <see cref="IOpenApiOperationTransformer"/> on the current <see cref="OpenApiOptions"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="transformer">The <see cref="IOpenApiOperationTransformer"/> instance to use.</param>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions AddOperationTransformer(IOpenApiOperationTransformer transformer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(transformer);
|
||||
|
||||
OperationTransformers.Add(transformer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a given delegate as an operation transformer on the current <see cref="OpenApiOptions"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="transformer">The delegate representing the operation transformer.</param>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions UseOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> transformer)
|
||||
public OpenApiOptions AddOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> transformer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(transformer, nameof(transformer));
|
||||
ArgumentNullException.ThrowIfNull(transformer);
|
||||
|
||||
DocumentTransformers.Add(new DelegateOpenApiDocumentTransformer(transformer));
|
||||
OperationTransformers.Add(new DelegateOpenApiOperationTransformer(transformer));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new schema transformer on the current <see cref="OpenApiOptions"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTransformerType">The type of the <see cref="IOpenApiSchemaTransformer"/> to instantiate.</typeparam>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions AddSchemaTransformer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TTransformerType>()
|
||||
where TTransformerType : IOpenApiSchemaTransformer
|
||||
{
|
||||
SchemaTransformers.Add(new TypeBasedOpenApiSchemaTransformer(typeof(TTransformerType)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a given instance of <see cref="IOpenApiOperationTransformer"/> on the current <see cref="OpenApiOptions"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="transformer">The <see cref="IOpenApiOperationTransformer"/> instance to use.</param>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions AddSchemaTransformer(IOpenApiSchemaTransformer transformer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(transformer);
|
||||
|
||||
SchemaTransformers.Add(transformer);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -113,11 +164,11 @@ public sealed class OpenApiOptions
|
|||
/// </summary>
|
||||
/// <param name="transformer">The delegate representing the schema transformer.</param>
|
||||
/// <returns>The <see cref="OpenApiOptions"/> instance for further customization.</returns>
|
||||
public OpenApiOptions UseSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> transformer)
|
||||
public OpenApiOptions AddSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> transformer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(transformer, nameof(transformer));
|
||||
ArgumentNullException.ThrowIfNull(transformer);
|
||||
|
||||
SchemaTransformers.Add(transformer);
|
||||
SchemaTransformers.Add(new DelegateOpenApiSchemaTransformer(transformer));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ internal sealed class OpenApiSchemaService(
|
|||
for (var i = 0; i < _openApiOptions.SchemaTransformers.Count; i++)
|
||||
{
|
||||
var transformer = _openApiOptions.SchemaTransformers[i];
|
||||
await transformer(schema, context, cancellationToken);
|
||||
await transformer.TransformAsync(schema, context, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,34 +31,10 @@ internal sealed class DelegateOpenApiDocumentTransformer : IOpenApiDocumentTrans
|
|||
if (_operationTransformer != null)
|
||||
{
|
||||
var documentService = context.ApplicationServices.GetRequiredKeyedService<OpenApiDocumentService>(context.DocumentName);
|
||||
foreach (var pathItem in document.Paths.Values)
|
||||
{
|
||||
for (var i = 0; i < OpenApiConstants.OperationTypes.Length; i++)
|
||||
{
|
||||
var operationType = OpenApiConstants.OperationTypes[i];
|
||||
if (!pathItem.Operations.TryGetValue(operationType, out var operation))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operation.Extensions.TryGetValue(OpenApiConstants.DescriptionId, out var descriptionIdExtension) &&
|
||||
descriptionIdExtension is ScrubbedOpenApiAny { Value: string descriptionId } &&
|
||||
documentService.TryGetCachedOperationTransformerContext(descriptionId, out var operationContext))
|
||||
{
|
||||
await _operationTransformer(operation, operationContext, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the cached operation transformer context was not found, throw an exception.
|
||||
// This can occur if the `x-aspnetcore-id` extension attribute was removed by the
|
||||
// user in another operation transformer or if the lookup for operation transformer
|
||||
// context resulted in a cache miss. As an alternative here, we could just to implement
|
||||
// the "slow-path" and look up the ApiDescription associated with the OpenApiOperation
|
||||
// using the OperationType and given path, but we'll avoid this for now.
|
||||
throw new InvalidOperationException("Cached operation transformer context not found. Please ensure that the operation contains the `x-aspnetcore-id` extension attribute.");
|
||||
}
|
||||
}
|
||||
}
|
||||
await documentService.ForEachOperationAsync(
|
||||
document,
|
||||
async (operation, operationContext, token) => await _operationTransformer(operation, operationContext, token),
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.OpenApi;
|
||||
|
||||
internal sealed class DelegateOpenApiOperationTransformer : IOpenApiOperationTransformer
|
||||
{
|
||||
private readonly Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> _transformer;
|
||||
|
||||
public DelegateOpenApiOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> transformer)
|
||||
{
|
||||
_transformer = transformer;
|
||||
}
|
||||
|
||||
public async Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await _transformer(operation, context, cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.OpenApi;
|
||||
|
||||
internal sealed class DelegateOpenApiSchemaTransformer : IOpenApiSchemaTransformer
|
||||
{
|
||||
private readonly Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> _transformer;
|
||||
|
||||
public DelegateOpenApiSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> transformer)
|
||||
{
|
||||
_transformer = transformer;
|
||||
}
|
||||
|
||||
public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await _transformer(schema, context, cancellationToken);
|
||||
}
|
||||
}
|
|
@ -17,5 +17,5 @@ public interface IOpenApiDocumentTransformer
|
|||
/// <param name="context">The <see cref="OpenApiDocumentTransformerContext"/> associated with the <see paramref="document"/>.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to use.</param>
|
||||
/// <returns>The task object representing the asynchronous operation.</returns>
|
||||
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken);
|
||||
Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a transformer that can be used to modify an OpenAPI operation.
|
||||
/// </summary>
|
||||
public interface IOpenApiOperationTransformer
|
||||
{
|
||||
/// <summary>
|
||||
/// Transforms the specified OpenAPI operation.
|
||||
/// </summary>
|
||||
/// <param name="operation">The <see cref="OpenApiOperation"/> to modify.</param>
|
||||
/// <param name="context">The <see cref="OpenApiOperationTransformerContext"/> associated with the <see paramref="operation"/>.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to use.</param>
|
||||
/// <returns>The task object representing the asynchronous operation.</returns>
|
||||
Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a transformer that can be used to modify an OpenAPI schema.
|
||||
/// </summary>
|
||||
public interface IOpenApiSchemaTransformer
|
||||
{
|
||||
/// <summary>
|
||||
/// Transforms the specified OpenAPI schema.
|
||||
/// </summary>
|
||||
/// <param name="schema">The <see cref="OpenApiSchema"/> to modify.</param>
|
||||
/// <param name="context">The <see cref="OpenApiSchemaTransformerContext"/> associated with the <see paramref="schema"/>.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to use.</param>
|
||||
/// <returns>The task object representing the asynchronous operation.</returns>
|
||||
Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.OpenApi;
|
||||
|
||||
internal sealed class TypeBasedOpenApiOperationTransformer : IOpenApiOperationTransformer
|
||||
{
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||
private readonly Type _transformerType;
|
||||
private readonly ObjectFactory _transformerFactory;
|
||||
|
||||
internal TypeBasedOpenApiOperationTransformer([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type transformerType)
|
||||
{
|
||||
_transformerType = transformerType;
|
||||
_transformerFactory = ActivatorUtilities.CreateFactory(_transformerType, []);
|
||||
}
|
||||
|
||||
public async Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var transformer = _transformerFactory.Invoke(context.ApplicationServices, []) as IOpenApiOperationTransformer;
|
||||
Debug.Assert(transformer is not null, $"The type {_transformerType} does not implement {nameof(IOpenApiOperationTransformer)}.");
|
||||
|
||||
try
|
||||
{
|
||||
await transformer.TransformAsync(operation, context, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (transformer is IAsyncDisposable asyncDisposable)
|
||||
{
|
||||
await asyncDisposable.DisposeAsync();
|
||||
}
|
||||
else if (transformer is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.OpenApi;
|
||||
|
||||
internal sealed class TypeBasedOpenApiSchemaTransformer : IOpenApiSchemaTransformer
|
||||
{
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||
private readonly Type _transformerType;
|
||||
private readonly ObjectFactory _transformerFactory;
|
||||
|
||||
internal TypeBasedOpenApiSchemaTransformer([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type transformerType)
|
||||
{
|
||||
_transformerType = transformerType;
|
||||
_transformerFactory = ActivatorUtilities.CreateFactory(_transformerType, []);
|
||||
}
|
||||
|
||||
public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var transformer = _transformerFactory.Invoke(context.ApplicationServices, []) as IOpenApiSchemaTransformer;
|
||||
Debug.Assert(transformer != null, $"The type {_transformerType} does not implement {nameof(IOpenApiSchemaTransformer)}.");
|
||||
try
|
||||
{
|
||||
await transformer.TransformAsync(schema, context, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (transformer is IAsyncDisposable asyncDisposable)
|
||||
{
|
||||
await asyncDisposable.DisposeAsync();
|
||||
}
|
||||
else if (transformer is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,11 @@
|
|||
"tags": [
|
||||
"Sample"
|
||||
],
|
||||
"externalDocs": {
|
||||
"description": "Documentation for this OpenAPI endpoint",
|
||||
"url": "https://example.com/api/docs/operations/CreateUser"
|
||||
},
|
||||
"operationId": "CreateUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
|
@ -48,6 +53,10 @@
|
|||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Documentation for this OpenAPI schema",
|
||||
"url": "https://example.com/api/docs/schemas/array"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,12 @@ public class DocumentTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer((document, context, cancellationToken) =>
|
||||
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
||||
{
|
||||
document.Info.Description = "1";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
options.UseTransformer((document, context, cancellationToken) =>
|
||||
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal("1", document.Info.Description);
|
||||
document.Info.Description = "2";
|
||||
|
@ -45,7 +45,7 @@ public class DocumentTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer<ActivatedTransformer>();
|
||||
options.AddDocumentTransformer<ActivatedTransformer>();
|
||||
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
|
@ -62,7 +62,7 @@ public class DocumentTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer(new ActivatedTransformer());
|
||||
options.AddDocumentTransformer(new ActivatedTransformer());
|
||||
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
|
@ -79,7 +79,7 @@ public class DocumentTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/todo", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer<ActivatedTransformerWithDependency>();
|
||||
options.AddDocumentTransformer<ActivatedTransformerWithDependency>();
|
||||
|
||||
// Assert that singleton dependency is only instantiated once
|
||||
// regardless of the number of requests.
|
||||
|
@ -105,7 +105,7 @@ public class DocumentTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/todo", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer<ActivatedTransformerWithDependency>();
|
||||
options.AddDocumentTransformer<ActivatedTransformerWithDependency>();
|
||||
|
||||
// Assert that transient dependency is instantiated twice for each
|
||||
// request to the OpenAPI document.
|
||||
|
@ -131,7 +131,7 @@ public class DocumentTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer<DisposableTransformer>();
|
||||
options.AddDocumentTransformer<DisposableTransformer>();
|
||||
|
||||
DisposableTransformer.DisposeCount = 0;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
|
@ -150,7 +150,7 @@ public class DocumentTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer<AsyncDisposableTransformer>();
|
||||
options.AddDocumentTransformer<AsyncDisposableTransformer>();
|
||||
|
||||
AsyncDisposableTransformer.DisposeCount = 0;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
|
|
|
@ -267,7 +267,7 @@ public class OpenApiSchemaReferenceTransformerTests : OpenApiDocumentServiceTest
|
|||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
if (context.Type == typeof(Todo) && context.ParameterDescription is not null)
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ using Microsoft.OpenApi.Models;
|
|||
public class OpenApiOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void UseTransformer_WithDocumentTransformerDelegate()
|
||||
public void AddDocumentTransformer_WithDocumentTransformerDelegate()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
|
@ -18,47 +18,53 @@ public class OpenApiOptionsTests
|
|||
});
|
||||
|
||||
// Act
|
||||
var result = options.UseTransformer(transformer);
|
||||
var result = options.AddDocumentTransformer(transformer);
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.DocumentTransformers);
|
||||
Assert.IsType<DelegateOpenApiDocumentTransformer>(insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.OperationTransformers);
|
||||
Assert.Empty(options.SchemaTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseTransformer_WithDocumentTransformerInstance()
|
||||
public void AddDocumentTransformer_WithDocumentTransformerInstance()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
var transformer = new TestOpenApiDocumentTransformer();
|
||||
|
||||
// Act
|
||||
var result = options.UseTransformer(transformer);
|
||||
var result = options.AddDocumentTransformer(transformer);
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.DocumentTransformers);
|
||||
Assert.Same(transformer, insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.OperationTransformers);
|
||||
Assert.Empty(options.SchemaTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseTransformer_WithDocumentTransformerType()
|
||||
public void AddDocumentTransformer_WithDocumentTransformerType()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
|
||||
// Act
|
||||
var result = options.UseTransformer<TestOpenApiDocumentTransformer>();
|
||||
var result = options.AddDocumentTransformer<TestOpenApiDocumentTransformer>();
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.DocumentTransformers);
|
||||
Assert.IsType<TypeBasedOpenApiDocumentTransformer>(insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.OperationTransformers);
|
||||
Assert.Empty(options.SchemaTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseTransformer_WithOperationTransformerDelegate()
|
||||
public void AddOperationTransformer_WithOperationTransformerDelegate()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
|
@ -69,12 +75,106 @@ public class OpenApiOptionsTests
|
|||
});
|
||||
|
||||
// Act
|
||||
var result = options.UseOperationTransformer(transformer);
|
||||
var result = options.AddOperationTransformer(transformer);
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.DocumentTransformers);
|
||||
Assert.IsType<DelegateOpenApiDocumentTransformer>(insertedTransformer);
|
||||
var insertedTransformer = Assert.Single(options.OperationTransformers);
|
||||
Assert.IsType<DelegateOpenApiOperationTransformer>(insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.DocumentTransformers);
|
||||
Assert.Empty(options.SchemaTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOperationTransformer_WithOperationTransformerInstance()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
var transformer = new TestOpenApiOperationTransformer();
|
||||
|
||||
// Act
|
||||
var result = options.AddOperationTransformer(transformer);
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.OperationTransformers);
|
||||
Assert.Same(transformer, insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.DocumentTransformers);
|
||||
Assert.Empty(options.SchemaTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOperationTransformer_WithOperationTransformerType()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
|
||||
// Act
|
||||
var result = options.AddOperationTransformer<TestOpenApiOperationTransformer>();
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.OperationTransformers);
|
||||
Assert.IsType<TypeBasedOpenApiOperationTransformer>(insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.DocumentTransformers);
|
||||
Assert.Empty(options.SchemaTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSchemaTransformer_WithSchemaTransformerDelegate()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
var transformer = new Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task>((schema, context, cancellationToken) =>
|
||||
{
|
||||
schema.Description = "New Description";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = options.AddSchemaTransformer(transformer);
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.SchemaTransformers);
|
||||
Assert.IsType<DelegateOpenApiSchemaTransformer>(insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.DocumentTransformers);
|
||||
Assert.Empty(options.OperationTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSchemaTransformer_WithSchemaTransformerInstance()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
var transformer = new TestOpenApiSchemaTransformer();
|
||||
|
||||
// Act
|
||||
var result = options.AddSchemaTransformer(transformer);
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.SchemaTransformers);
|
||||
Assert.Same(transformer, insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.DocumentTransformers);
|
||||
Assert.Empty(options.OperationTransformers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSchemaTransformer_WithSchemaTransformerType()
|
||||
{
|
||||
// Arrange
|
||||
var options = new OpenApiOptions();
|
||||
|
||||
// Act
|
||||
var result = options.AddSchemaTransformer<TestOpenApiSchemaTransformer>();
|
||||
|
||||
// Assert
|
||||
var insertedTransformer = Assert.Single(options.SchemaTransformers);
|
||||
Assert.IsType<TypeBasedOpenApiSchemaTransformer>(insertedTransformer);
|
||||
Assert.IsType<OpenApiOptions>(result);
|
||||
Assert.Empty(options.DocumentTransformers);
|
||||
Assert.Empty(options.OperationTransformers);
|
||||
}
|
||||
|
||||
private class TestOpenApiDocumentTransformer : IOpenApiDocumentTransformer
|
||||
|
@ -84,4 +184,20 @@ public class OpenApiOptionsTests
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestOpenApiOperationTransformer : IOpenApiOperationTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestOpenApiSchemaTransformer : IOpenApiSchemaTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
public class OperationTransformerTests : OpenApiDocumentServiceTestBase
|
||||
{
|
||||
|
@ -15,7 +18,7 @@ public class OperationTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseOperationTransformer((operation, context, cancellationToken) =>
|
||||
options.AddOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
var apiDescription = context.Description;
|
||||
operation.Description = apiDescription.RelativePath;
|
||||
|
@ -41,7 +44,7 @@ public class OperationTransformerTests : OpenApiDocumentServiceTestBase
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_RunsInRegisteredOrder()
|
||||
public async Task OperationTransformers_RunInRegisteredOrder()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
|
@ -49,21 +52,40 @@ public class OperationTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseOperationTransformer((operation, context, cancellationToken) =>
|
||||
|
||||
// While added first, document transformers should run after the operation transformers
|
||||
options.AddDocumentTransformer<MyDocumentationTransformer>();
|
||||
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
||||
{
|
||||
Assert.All(document.Paths.Values.SelectMany(p => p.Operations).Select(p => p.Value), o => Assert.Equal("6", o.Description));
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// Operation transforms should run FIFO regardless of which kind of transformer is used
|
||||
options.AddOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Null(operation.Description);
|
||||
operation.Description = "1";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
options.UseOperationTransformer((operation, context, cancellationToken) =>
|
||||
options.AddOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal("1", operation.Description);
|
||||
operation.Description = "2";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
options.UseOperationTransformer((operation, context, cancellationToken) =>
|
||||
options.AddOperationTransformer<MyOperationTransformer3>();
|
||||
options.AddOperationTransformer(new MyOperationTransformer4());
|
||||
options.AddOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal("2", operation.Description);
|
||||
operation.Description = "3";
|
||||
Assert.Equal("4", operation.Description);
|
||||
operation.Description = "5";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
options.AddOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal("5", operation.Description);
|
||||
operation.Description = "6";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
|
@ -74,17 +96,46 @@ public class OperationTransformerTests : OpenApiDocumentServiceTestBase
|
|||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("3", operation.Description);
|
||||
Assert.Equal("6", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("3", operation.Description);
|
||||
Assert.Equal("6", operation.Description);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class MyDocumentationTransformer : IOpenApiDocumentTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
Assert.All(document.Paths.Values.SelectMany(p => p.Operations).Select(p => p.Value), o => Assert.Equal("6", o.Description));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MyOperationTransformer3 : IOpenApiOperationTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
Assert.Equal("2", operation.Description);
|
||||
operation.Description = "3";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MyOperationTransformer4 : IOpenApiOperationTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
Assert.Equal("3", operation.Description);
|
||||
operation.Description = "4";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_CanMutateOperationViaDocumentTransformer()
|
||||
{
|
||||
|
@ -94,7 +145,7 @@ public class OperationTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseTransformer((document, context, cancellationToken) =>
|
||||
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
||||
{
|
||||
foreach (var pathItem in document.Paths.Values)
|
||||
{
|
||||
|
@ -125,24 +176,320 @@ public class OperationTransformerTests : OpenApiDocumentServiceTestBase
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_ThrowsExceptionIfDescriptionIdNotFound()
|
||||
public async Task OperationTransformer_CanMutateOperationViaOperationTransformer()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapGet("/todo", () => { });
|
||||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
operation.Extensions.Remove("x-aspnetcore-id");
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
options.UseOperationTransformer((operation, context, cancellationToken) =>
|
||||
options.AddOperationTransformer((operation, context, cancellationToken) =>
|
||||
{
|
||||
operation.Description = "3";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => VerifyOpenApiDocument(builder, options, _ => { }));
|
||||
Assert.Equal("Cached operation transformer context not found. Please ensure that the operation contains the `x-aspnetcore-id` extension attribute.", exception.Message);
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("3", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("3", operation.Description);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_SupportsActivatedTransformers()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapGet("/todo", () => { });
|
||||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddOperationTransformer<ActivatedTransformer>();
|
||||
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_SupportsInstanceTransformers()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapGet("/todo", () => { });
|
||||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddOperationTransformer(new ActivatedTransformer());
|
||||
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_SupportsActivatedTransformerWithSingletonDependency()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection().AddSingleton<Dependency>();
|
||||
var builder = CreateBuilder(serviceCollection);
|
||||
|
||||
builder.MapGet("/todo", () => { });
|
||||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddOperationTransformer<ActivatedTransformerWithDependency>();
|
||||
|
||||
// Assert that singleton dependency is only instantiated once
|
||||
// regardless of the number of requests and operations.
|
||||
string description = null;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
description = operation.Description;
|
||||
Assert.Equal(Dependency.InstantiationCount.ToString(CultureInfo.InvariantCulture), description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal(description, operation.Description);
|
||||
});
|
||||
});
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal(description, operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal(description, operation.Description);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_SupportsActivatedTransformerWithTransientDependency()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection().AddTransient<Dependency>();
|
||||
var builder = CreateBuilder(serviceCollection);
|
||||
|
||||
builder.MapGet("/todo", () => { });
|
||||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddOperationTransformer<ActivatedTransformerWithDependency>();
|
||||
|
||||
// Assert that transient dependency is instantiated once for each operation.
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("1", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("2", operation.Description);
|
||||
});
|
||||
});
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("3", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("4", operation.Description);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_SupportsDisposableActivatedTransformer()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapGet("/todo", () => { });
|
||||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddOperationTransformer<DisposableTransformer>();
|
||||
|
||||
DisposableTransformer.DisposeCount = 0;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
});
|
||||
});
|
||||
Assert.Equal(2, DisposableTransformer.DisposeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationTransformer_SupportsAsyncDisposableActivatedTransformer()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapGet("/todo", () => { });
|
||||
builder.MapGet("/user", () => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddOperationTransformer<AsyncDisposableTransformer>();
|
||||
|
||||
AsyncDisposableTransformer.DisposeCount = 0;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
Assert.Collection(document.Paths.OrderBy(p => p.Key),
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/todo", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
},
|
||||
path =>
|
||||
{
|
||||
Assert.Equal("/user", path.Key);
|
||||
var operation = Assert.Single(path.Value.Operations.Values);
|
||||
Assert.Equal("Operation Description", operation.Description);
|
||||
});
|
||||
});
|
||||
Assert.Equal(2, AsyncDisposableTransformer.DisposeCount);
|
||||
}
|
||||
|
||||
private class ActivatedTransformer : IOpenApiOperationTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
operation.Description = "Operation Description";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class DisposableTransformer : IOpenApiOperationTransformer, IDisposable
|
||||
{
|
||||
internal bool Disposed = false;
|
||||
internal static int DisposeCount = 0;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
DisposeCount += 1;
|
||||
}
|
||||
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
operation.Description = "Operation Description";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncDisposableTransformer : IOpenApiOperationTransformer, IAsyncDisposable
|
||||
{
|
||||
internal bool Disposed = false;
|
||||
internal static int DisposeCount = 0;
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
Disposed = true;
|
||||
DisposeCount += 1;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
operation.Description = "Operation Description";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class ActivatedTransformerWithDependency(Dependency dependency) : IOpenApiOperationTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
dependency.TestMethod();
|
||||
operation.Description = Dependency.InstantiationCount.ToString(CultureInfo.InvariantCulture);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class Dependency
|
||||
{
|
||||
public Dependency()
|
||||
{
|
||||
InstantiationCount += 1;
|
||||
}
|
||||
|
||||
internal void TestMethod() { }
|
||||
|
||||
internal static int InstantiationCount = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
@ -17,7 +18,7 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal(typeof(Todo), context.Type);
|
||||
Assert.Equal("todo", context.ParameterDescription.Name);
|
||||
|
@ -35,7 +36,7 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal(typeof(Todo), context.Type);
|
||||
Assert.Null(context.ParameterDescription);
|
||||
|
@ -53,7 +54,7 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
var service = context.ApplicationServices.GetKeyedService<OpenApiDocumentService>(context.DocumentName);
|
||||
Assert.NotNull(service);
|
||||
|
@ -74,7 +75,7 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
cts.Cancel();
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal(cts.Token, cancellationToken);
|
||||
Assert.True(cancellationToken.IsCancellationRequested);
|
||||
|
@ -92,12 +93,12 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
schema.Extensions["x-my-extension"] = new OpenApiString("1");
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
Assert.Equal("1", ((OpenApiString)schema.Extensions["x-my-extension"]).Value);
|
||||
schema.Extensions["x-my-extension"] = new OpenApiString("2");
|
||||
|
@ -121,7 +122,7 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
if (context.Type == typeof(Todo))
|
||||
{
|
||||
|
@ -151,7 +152,7 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.UseSchemaTransformer((schema, context, cancellationToken) =>
|
||||
options.AddSchemaTransformer((schema, context, cancellationToken) =>
|
||||
{
|
||||
if (context.Type == typeof(Todo) && context.ParameterDescription is not null)
|
||||
{
|
||||
|
@ -171,4 +172,248 @@ public class SchemaTransformerTests : OpenApiDocumentServiceTestBase
|
|||
Assert.False(responseSchema.Extensions.TryGetValue("x-my-extension", out var _));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchemaTransformer_SupportsActivatedTransformers()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddSchemaTransformer<ActivatedTransformer>();
|
||||
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("1", ((OpenApiString)requestSchema.Extensions["x-my-extension"]).Value);
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("1", ((OpenApiString)responseSchema.Extensions["x-my-extension"]).Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchemaTransformer_SupportsInstanceTransformers()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddSchemaTransformer(new ActivatedTransformer());
|
||||
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("1", ((OpenApiString)requestSchema.Extensions["x-my-extension"]).Value);
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("1", ((OpenApiString)responseSchema.Extensions["x-my-extension"]).Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchemaTransformer_SupportsActivatedTransformerWithSingletonDependency()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection().AddSingleton<Dependency>();
|
||||
var builder = CreateBuilder(serviceCollection);
|
||||
|
||||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddSchemaTransformer<ActivatedTransformerWithDependency>();
|
||||
|
||||
// Assert that singleton dependency is only instantiated once
|
||||
// regardless of the number of requests, operations or schemas.
|
||||
string value = null;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
value = ((OpenApiString)requestSchema.Extensions["x-my-extension"]).Value;
|
||||
Assert.Equal(Dependency.InstantiationCount.ToString(CultureInfo.InvariantCulture), value);
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal(value, ((OpenApiString)responseSchema.Extensions["x-my-extension"]).Value);
|
||||
});
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal(value, ((OpenApiString)requestSchema.Extensions["x-my-extension"]).Value);
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal(value, ((OpenApiString)responseSchema.Extensions["x-my-extension"]).Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchemaTransformer_SupportsActivatedTransformerWithTransientDependency()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection().AddTransient<Dependency>();
|
||||
var builder = CreateBuilder(serviceCollection);
|
||||
|
||||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddSchemaTransformer<ActivatedTransformerWithDependency>();
|
||||
|
||||
// Assert that transient dependency is instantiated once for each
|
||||
// request to the OpenAPI document for each created schema.
|
||||
var countBefore = Dependency.InstantiationCount;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.True(requestSchema.Extensions.ContainsKey("x-my-extension"));
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.True(responseSchema.Extensions.ContainsKey("x-my-extension"));
|
||||
});
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.True(requestSchema.Extensions.ContainsKey("x-my-extension"));
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.True(responseSchema.Extensions.ContainsKey("x-my-extension"));
|
||||
});
|
||||
var countAfter = Dependency.InstantiationCount;
|
||||
Assert.Equal(countBefore + 4, countAfter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchemaTransformer_SupportsDisposableActivatedTransformer()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddSchemaTransformer<DisposableTransformer>();
|
||||
|
||||
DisposableTransformer.DisposeCount = 0;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("Schema Description", requestSchema.Description);
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("Schema Description", responseSchema.Description);
|
||||
});
|
||||
Assert.Equal(2, DisposableTransformer.DisposeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchemaTransformer_SupportsAsyncDisposableActivatedTransformer()
|
||||
{
|
||||
var builder = CreateBuilder();
|
||||
|
||||
builder.MapPost("/todo", (Todo todo) => { });
|
||||
builder.MapGet("/todo", () => new Todo(1, "Item1", false, DateTime.Now));
|
||||
|
||||
var options = new OpenApiOptions();
|
||||
options.AddSchemaTransformer<AsyncDisposableTransformer>();
|
||||
|
||||
AsyncDisposableTransformer.DisposeCount = 0;
|
||||
await VerifyOpenApiDocument(builder, options, document =>
|
||||
{
|
||||
var path = Assert.Single(document.Paths.Values);
|
||||
var postOperation = path.Operations[OperationType.Post];
|
||||
var requestSchema = postOperation.RequestBody.Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("Schema Description", requestSchema.Description);
|
||||
var getOperation = path.Operations[OperationType.Get];
|
||||
var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema.GetEffective(document);
|
||||
Assert.Equal("Schema Description", responseSchema.Description);
|
||||
});
|
||||
Assert.Equal(2, AsyncDisposableTransformer.DisposeCount);
|
||||
}
|
||||
|
||||
private class ActivatedTransformer : IOpenApiSchemaTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (context.Type == typeof(Todo))
|
||||
{
|
||||
schema.Extensions["x-my-extension"] = new OpenApiString("1");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class DisposableTransformer : IOpenApiSchemaTransformer, IDisposable
|
||||
{
|
||||
internal bool Disposed = false;
|
||||
internal static int DisposeCount = 0;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
DisposeCount += 1;
|
||||
}
|
||||
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
schema.Description = "Schema Description";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncDisposableTransformer : IOpenApiSchemaTransformer, IAsyncDisposable
|
||||
{
|
||||
internal bool Disposed = false;
|
||||
internal static int DisposeCount = 0;
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
Disposed = true;
|
||||
DisposeCount += 1;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
schema.Description = "Schema Description";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class ActivatedTransformerWithDependency(Dependency dependency) : IOpenApiSchemaTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
dependency.TestMethod();
|
||||
schema.Extensions["x-my-extension"] = new OpenApiString(Dependency.InstantiationCount.ToString(CultureInfo.InvariantCulture));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class Dependency
|
||||
{
|
||||
public Dependency()
|
||||
{
|
||||
InstantiationCount += 1;
|
||||
}
|
||||
|
||||
internal void TestMethod() { }
|
||||
|
||||
internal static int InstantiationCount = 0;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче