Skip to content

Commit f257378

Browse files
authored
Add support for generating JSON schemas in OpenAPI doc (#55208)
1 parent 7bba81e commit f257378

File tree

38 files changed

+5266
-35
lines changed

38 files changed

+5266
-35
lines changed

eng/Dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ and are generated based on the last package release.
228228
<LatestPackageReference Include="StackExchange.Redis" />
229229
<LatestPackageReference Include="Swashbuckle.AspNetCore" />
230230
<LatestPackageReference Include="System.Reactive.Linq" />
231+
<LatestPackageReference Include="Verify.Xunit" />
231232
<LatestPackageReference Include="xunit.abstractions" />
232233
<LatestPackageReference Include="xunit.analyzers" />
233234
<LatestPackageReference Include="xunit.assert" />

eng/Versions.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@
326326
<StackExchangeRedisVersion>2.7.27</StackExchangeRedisVersion>
327327
<SystemReactiveLinqVersion>5.0.0</SystemReactiveLinqVersion>
328328
<SwashbuckleAspNetCoreVersion>6.4.0</SwashbuckleAspNetCoreVersion>
329+
<VerifyXunitVersion>19.14.0</VerifyXunitVersion>
329330
<XunitAbstractionsVersion>2.0.3</XunitAbstractionsVersion>
330331
<XunitAnalyzersVersion>1.0.0</XunitAnalyzersVersion>
331332
<XunitVersion>2.4.2</XunitVersion>

src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
9090
<InternalsVisibleTo Include="Microsoft.AspNetCore.Mvc.ViewFeatures.Test" />
9191
<InternalsVisibleTo Include="Microsoft.AspNetCore.Mvc.Views.TestCommon" />
9292
<InternalsVisibleTo Include="Microsoft.AspNetCore.Mvc.Microbenchmarks" />
93+
<InternalsVisibleTo Include="Microsoft.AspNetCore.OpenApi.Tests" />
9394
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="$(MoqPublicKey)" />
9495
</ItemGroup>
9596
</Project>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
[ApiController]
5+
[Route("[controller]")]
6+
public class TestController : ControllerBase
7+
{
8+
[HttpGet]
9+
[Route("/getbyidandname/{id}/{name}")]
10+
public string GetByIdAndName(RouteParamsContainer paramsContainer)
11+
{
12+
return paramsContainer.Id + "_" + paramsContainer.Name;
13+
}
14+
15+
public class RouteParamsContainer
16+
{
17+
[FromRoute]
18+
public int Id { get; set; }
19+
20+
[FromRoute]
21+
[MinLength(5)]
22+
public string? Name { get; set; }
23+
}
24+
}

src/OpenApi/sample/Program.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
var builder = WebApplication.CreateBuilder(args);
99

10+
builder.Services.AddControllers();
1011
builder.Services.AddAuthentication().AddJwtBearer();
1112

1213
builder.Services.AddOpenApi("v1", options =>
@@ -51,6 +52,8 @@
5152
var responses = app.MapGroup("responses")
5253
.WithGroupName("responses");
5354

55+
v1.MapGet("/array-of-guids", (Guid[] guids) => guids);
56+
5457
v1.MapPost("/todos", (Todo todo) => Results.Created($"/todos/{todo.Id}", todo))
5558
.WithSummary("Creates a new todo item.");
5659
v1.MapGet("/todos/{id}", (int id) => new TodoWithDueDate(1, "Test todo", false, DateTime.Now.AddDays(1), DateTime.Now))
@@ -67,4 +70,13 @@
6770
responses.MapGet("/200-only-xml", () => new TodoWithDueDate(1, "Test todo", false, DateTime.Now.AddDays(1), DateTime.Now))
6871
.Produces<Todo>(contentType: "text/xml");
6972

73+
responses.MapGet("/triangle", () => new Triangle { Color = "red", Sides = 3, Hypotenuse = 5.0 });
74+
responses.MapGet("/shape", () => new Shape { Color = "blue", Sides = 4 });
75+
76+
app.MapControllers();
77+
7078
app.Run();
79+
80+
// Make Program class public to support snapshot testing
81+
// against sample app using WebApplicationFactory.
82+
public partial class Program { }

src/OpenApi/sample/Transformers/OperationTransformers.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public static OpenApiOptions AddHeader(this OpenApiOptions options, string heade
1616
{
1717
var schema = OpenApiTypeMapper.MapTypeToOpenApiPrimitiveType(typeof(string));
1818
schema.Default = new OpenApiString(defaultValue);
19+
operation.Parameters ??= [];
1920
operation.Parameters.Add(new OpenApiParameter
2021
{
2122
Name = headerName,

src/OpenApi/src/Extensions/JsonObjectSchemaExtensions.cs

Lines changed: 292 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text.Json.Serialization.Metadata;
7+
8+
namespace JsonSchemaMapper;
9+
10+
/// <summary>
11+
/// Defines the context in which a JSON schema within a type graph is being generated.
12+
/// </summary>
13+
#if EXPOSE_JSON_SCHEMA_MAPPER
14+
public
15+
#else
16+
internal
17+
#endif
18+
readonly struct JsonSchemaGenerationContext
19+
{
20+
internal JsonSchemaGenerationContext(JsonTypeInfo typeInfo, Type? declaringType, JsonPropertyInfo? propertyInfo, ParameterInfo? parameterInfo)
21+
{
22+
TypeInfo = typeInfo;
23+
DeclaringType = declaringType;
24+
PropertyInfo = propertyInfo;
25+
ParameterInfo = parameterInfo;
26+
}
27+
28+
/// <summary>
29+
/// The <see cref="JsonTypeInfo"/> for the type being processed.
30+
/// </summary>
31+
public JsonTypeInfo TypeInfo { get; }
32+
33+
/// <summary>
34+
/// The declaring type of the property or parameter being processed.
35+
/// </summary>
36+
public Type? DeclaringType { get; }
37+
38+
/// <summary>
39+
/// The <see cref="JsonPropertyInfo"/> if the schema is being generated for a property.
40+
/// </summary>
41+
public JsonPropertyInfo? PropertyInfo { get; }
42+
43+
/// <summary>
44+
/// The <see cref="System.Reflection.ParameterInfo"/> if a constructor parameter
45+
/// has been associated with the accompanying <see cref="PropertyInfo"/>.
46+
/// </summary>
47+
public ParameterInfo? ParameterInfo { get; }
48+
49+
/// <summary>
50+
/// Checks if the type, property, or parameter has the specified attribute applied.
51+
/// </summary>
52+
/// <typeparam name="TAttribute">The type of the attribute to resolve.</typeparam>
53+
/// <param name="inherit">Whether to look up the hierarchy chain for the inherited custom attribute.</param>
54+
/// <returns>True if the attribute is defined by the current context.</returns>
55+
public bool IsDefined<TAttribute>(bool inherit = false)
56+
where TAttribute : Attribute =>
57+
GetCustomAttributes(typeof(TAttribute), inherit).Any();
58+
59+
/// <summary>
60+
/// Checks if the type, property, or parameter has the specified attribute applied.
61+
/// </summary>
62+
/// <typeparam name="TAttribute">The type of the attribute to resolve.</typeparam>
63+
/// <param name="inherit">Whether to look up the hierarchy chain for the inherited custom attribute.</param>
64+
/// <returns>The first attribute resolved from the current context, or null.</returns>
65+
public TAttribute? GetAttribute<TAttribute>(bool inherit = false)
66+
where TAttribute : Attribute =>
67+
(TAttribute?)GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault();
68+
69+
/// <summary>
70+
/// Resolves any custom attributes that might have been applied to the type, property, or parameter.
71+
/// </summary>
72+
/// <param name="type">The attribute type to resolve.</param>
73+
/// <param name="inherit">Whether to look up the hierarchy chain for the inherited custom attribute.</param>
74+
/// <returns>An enumerable of all custom attributes defined by the context.</returns>
75+
public IEnumerable<Attribute> GetCustomAttributes(Type type, bool inherit = false)
76+
{
77+
// Resolves attributes starting from the property, then the parameter, and finally the type itself.
78+
return GetAttrs(JsonSchemaMapper.ResolveAttributeProvider(DeclaringType, PropertyInfo))
79+
.Concat(GetAttrs(ParameterInfo))
80+
.Concat(GetAttrs(TypeInfo.Type))
81+
.Cast<Attribute>();
82+
83+
object[] GetAttrs(ICustomAttributeProvider? provider) =>
84+
provider?.GetCustomAttributes(type, inherit) ?? Array.Empty<object>();
85+
}
86+
}

0 commit comments

Comments
 (0)