Skip to content

Commit 613c1e9

Browse files
authored
Re-plat on JsonExporter APIs and remove JsonSchemaMapper prototype (#56330)
1 parent 7712869 commit 613c1e9

16 files changed

+302
-2473
lines changed

src/OpenApi/src/Extensions/JsonObjectSchemaExtensions.cs renamed to src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
using System.Reflection;
99
using System.Text.Json;
1010
using System.Text.Json.Nodes;
11+
using System.Text.Json.Schema;
1112
using System.Text.Json.Serialization.Metadata;
12-
using JsonSchemaMapper;
1313
using Microsoft.AspNetCore.Mvc.ApiExplorer;
1414
using Microsoft.AspNetCore.Mvc.Infrastructure;
1515
using Microsoft.AspNetCore.Routing;
@@ -22,8 +22,10 @@ namespace Microsoft.AspNetCore.OpenApi;
2222
/// Provides a set of extension methods for modifying the opaque JSON Schema type
2323
/// that is provided by the underlying schema generator in System.Text.Json.
2424
/// </summary>
25-
internal static class JsonObjectSchemaExtensions
25+
internal static class JsonNodeSchemaExtensions
2626
{
27+
private static readonly NullabilityInfoContext _nullabilityInfoContext = new();
28+
2729
private static readonly Dictionary<Type, OpenApiSchema> _simpleTypeToOpenApiSchema = new()
2830
{
2931
[typeof(bool)] = new() { Type = "boolean" },
@@ -43,6 +45,8 @@ internal static class JsonObjectSchemaExtensions
4345
[typeof(char)] = new() { Type = "string" },
4446
[typeof(Uri)] = new() { Type = "string", Format = "uri" },
4547
[typeof(string)] = new() { Type = "string" },
48+
[typeof(TimeOnly)] = new() { Type = "string", Format = "time" },
49+
[typeof(DateOnly)] = new() { Type = "string", Format = "date" },
4650
};
4751

4852
/// <summary>
@@ -52,7 +56,7 @@ internal static class JsonObjectSchemaExtensions
5256
/// OpenApi schema v3 supports the validation vocabulary supported by JSON Schema. Because the underlying
5357
/// schema generator does not handle validation attributes to the validation vocabulary, we apply that mapping here.
5458
///
55-
/// Note that this method targets <see cref="JsonObject"/> and not <see cref="OpenApiSchema"/> because it is
59+
/// Note that this method targets <see cref="JsonNode"/> and not <see cref="OpenApiSchema"/> because it is
5660
/// designed to be invoked via the `OnGenerated` callback provided by the underlying schema generator
5761
/// so that attributes can be mapped to the properties associated with inputs and outputs to a given request.
5862
///
@@ -74,9 +78,9 @@ internal static class JsonObjectSchemaExtensions
7478
/// will result in the schema having a type of "string" and a format of "uri" even though the model binding
7579
/// layer will validate the string against *both* constraints.
7680
/// </remarks>
77-
/// <param name="schema">The <see cref="JsonObject"/> produced by the underlying schema generator.</param>
81+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
7882
/// <param name="validationAttributes">A list of the validation attributes to apply.</param>
79-
internal static void ApplyValidationAttributes(this JsonObject schema, IEnumerable<Attribute> validationAttributes)
83+
internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable<Attribute> validationAttributes)
8084
{
8185
foreach (var attribute in validationAttributes)
8286
{
@@ -126,10 +130,10 @@ internal static void ApplyValidationAttributes(this JsonObject schema, IEnumerab
126130
/// <summary>
127131
/// Populate the default value into the current schema.
128132
/// </summary>
129-
/// <param name="schema">The <see cref="JsonObject"/> produced by the underlying schema generator.</param>
133+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
130134
/// <param name="defaultValue">An object representing the <see cref="object"/> associated with the default value.</param>
131135
/// <param name="jsonTypeInfo">The <see cref="JsonTypeInfo"/> associated with the target type.</param>
132-
internal static void ApplyDefaultValue(this JsonObject schema, object? defaultValue, JsonTypeInfo? jsonTypeInfo)
136+
internal static void ApplyDefaultValue(this JsonNode schema, object? defaultValue, JsonTypeInfo? jsonTypeInfo)
133137
{
134138
if (jsonTypeInfo is null)
135139
{
@@ -159,29 +163,35 @@ internal static void ApplyDefaultValue(this JsonObject schema, object? defaultVa
159163
/// based on whether the underlying schema generator returned an array type containing "null" to
160164
/// represent a nullable type or if the type was denoted as nullable from our lookup cache.
161165
///
162-
/// Note that this method targets <see cref="JsonObject"/> and not <see cref="OpenApiSchema"/> because
166+
/// Note that this method targets <see cref="JsonNode"/> and not <see cref="OpenApiSchema"/> because
163167
/// it is is designed to be invoked via the `OnGenerated` callback in the underlying schema generator as
164168
/// opposed to after the generated schemas have been mapped to OpenAPI schemas.
165169
/// </remarks>
166-
/// <param name="schema">The <see cref="JsonObject"/> produced by the underlying schema generator.</param>
167-
/// <param name="context">The <see cref="JsonSchemaGenerationContext"/> associated with the <see paramref="schema"/>.</param>
168-
internal static void ApplyPrimitiveTypesAndFormats(this JsonObject schema, JsonSchemaGenerationContext context)
170+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
171+
/// <param name="context">The <see cref="JsonSchemaExporterContext"/> associated with the <see paramref="schema"/>.</param>
172+
internal static void ApplyPrimitiveTypesAndFormats(this JsonNode schema, JsonSchemaExporterContext context)
169173
{
170-
if (_simpleTypeToOpenApiSchema.TryGetValue(context.TypeInfo.Type, out var openApiSchema))
174+
var type = context.TypeInfo.Type;
175+
var underlyingType = Nullable.GetUnderlyingType(type);
176+
if (_simpleTypeToOpenApiSchema.TryGetValue(underlyingType ?? type, out var openApiSchema))
171177
{
172178
schema[OpenApiSchemaKeywords.NullableKeyword] = openApiSchema.Nullable || (schema[OpenApiSchemaKeywords.TypeKeyword] is JsonArray schemaType && schemaType.GetValues<string>().Contains("null"));
173179
schema[OpenApiSchemaKeywords.TypeKeyword] = openApiSchema.Type;
174180
schema[OpenApiSchemaKeywords.FormatKeyword] = openApiSchema.Format;
175181
schema[OpenApiConstants.SchemaId] = context.TypeInfo.GetSchemaReferenceId();
182+
schema[OpenApiSchemaKeywords.NullableKeyword] = underlyingType != null;
183+
// Clear out patterns that the underlying JSON schema generator uses to represent
184+
// validations for DateTime, DateTimeOffset, and integers.
185+
schema[OpenApiSchemaKeywords.PatternKeyword] = null;
176186
}
177187
}
178188

179189
/// <summary>
180190
/// Applies route constraints to the target schema.
181191
/// </summary>
182-
/// <param name="schema">The <see cref="JsonObject"/> produced by the underlying schema generator.</param>
192+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
183193
/// <param name="constraints">The list of <see cref="IRouteConstraint"/>s associated with the route parameter.</param>
184-
internal static void ApplyRouteConstraints(this JsonObject schema, IEnumerable<IRouteConstraint> constraints)
194+
internal static void ApplyRouteConstraints(this JsonNode schema, IEnumerable<IRouteConstraint> constraints)
185195
{
186196
// Apply constraints in reverse order because when it comes to the routing
187197
// layer the first constraint that is violated causes routing to short circuit.
@@ -255,10 +265,10 @@ internal static void ApplyRouteConstraints(this JsonObject schema, IEnumerable<I
255265
/// <summary>
256266
/// Applies parameter-specific customizations to the target schema.
257267
/// </summary>
258-
/// <param name="schema">The <see cref="JsonObject"/> produced by the underlying schema generator.</param>
268+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
259269
/// <param name="parameterDescription">The <see cref="ApiParameterDescription"/> associated with the <see paramref="schema"/>.</param>
260270
/// <param name="jsonTypeInfo">The <see cref="JsonTypeInfo"/> associated with the <see paramref="schema"/>.</param>
261-
internal static void ApplyParameterInfo(this JsonObject schema, ApiParameterDescription parameterDescription, JsonTypeInfo? jsonTypeInfo)
271+
internal static void ApplyParameterInfo(this JsonNode schema, ApiParameterDescription parameterDescription, JsonTypeInfo? jsonTypeInfo)
262272
{
263273
// This is special handling for parameters that are not bound from the body but represented in a complex type.
264274
// For example:
@@ -281,17 +291,24 @@ internal static void ApplyParameterInfo(this JsonObject schema, ApiParameterDesc
281291
{
282292
var attributes = validations.OfType<ValidationAttribute>();
283293
schema.ApplyValidationAttributes(attributes);
284-
if (parameterDescription.ParameterDescriptor is IParameterInfoParameterDescriptor { ParameterInfo: { } parameterInfo })
294+
}
295+
if (parameterDescription.ParameterDescriptor is IParameterInfoParameterDescriptor { ParameterInfo: { } parameterInfo })
296+
{
297+
if (parameterInfo.HasDefaultValue)
285298
{
286-
if (parameterInfo.HasDefaultValue)
287-
{
288-
schema.ApplyDefaultValue(parameterInfo.DefaultValue, jsonTypeInfo);
289-
}
290-
else if (parameterInfo.GetCustomAttributes<DefaultValueAttribute>().LastOrDefault() is { } defaultValueAttribute)
291-
{
292-
schema.ApplyDefaultValue(defaultValueAttribute.Value, jsonTypeInfo);
293-
}
299+
schema.ApplyDefaultValue(parameterInfo.DefaultValue, jsonTypeInfo);
300+
}
301+
else if (parameterInfo.GetCustomAttributes<DefaultValueAttribute>().LastOrDefault() is { } defaultValueAttribute)
302+
{
303+
schema.ApplyDefaultValue(defaultValueAttribute.Value, jsonTypeInfo);
304+
}
305+
306+
if (parameterInfo.GetCustomAttributes().OfType<ValidationAttribute>() is { } validationAttributes)
307+
{
308+
schema.ApplyValidationAttributes(validationAttributes);
294309
}
310+
311+
schema.ApplyNullabilityContextInfo(parameterInfo);
295312
}
296313
// Route constraints are only defined on parameters that are sourced from the path. Since
297314
// they are encoded in the route template, and not in the type information based to the underlying
@@ -305,9 +322,9 @@ internal static void ApplyParameterInfo(this JsonObject schema, ApiParameterDesc
305322
/// <summary>
306323
/// Applies the polymorphism options to the target schema following OpenAPI v3's conventions.
307324
/// </summary>
308-
/// <param name="schema">The <see cref="JsonObject"/> produced by the underlying schema generator.</param>
309-
/// <param name="context">The <see cref="JsonSchemaGenerationContext"/> associated with the current type.</param>
310-
internal static void ApplyPolymorphismOptions(this JsonObject schema, JsonSchemaGenerationContext context)
325+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
326+
/// <param name="context">The <see cref="JsonSchemaExporterContext"/> associated with the current type.</param>
327+
internal static void ApplyPolymorphismOptions(this JsonNode schema, JsonSchemaExporterContext context)
311328
{
312329
if (context.TypeInfo.PolymorphismOptions is { } polymorphismOptions)
313330
{
@@ -329,10 +346,48 @@ internal static void ApplyPolymorphismOptions(this JsonObject schema, JsonSchema
329346
/// <summary>
330347
/// Set the x-schema-id property on the schema to the identifier associated with the type.
331348
/// </summary>
332-
/// <param name="schema">The <see cref="JsonObject"/> produced by the underlying schema generator.</param>
333-
/// <param name="context">The <see cref="JsonSchemaGenerationContext"/> associated with the current type.</param>
334-
internal static void ApplySchemaReferenceId(this JsonObject schema, JsonSchemaGenerationContext context)
349+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
350+
/// <param name="context">The <see cref="JsonSchemaExporterContext"/> associated with the current type.</param>
351+
internal static void ApplySchemaReferenceId(this JsonNode schema, JsonSchemaExporterContext context)
335352
{
336353
schema[OpenApiConstants.SchemaId] = context.TypeInfo.GetSchemaReferenceId();
337354
}
355+
356+
/// <summary>
357+
/// Support applying nullability status for reference types provided as a parameter.
358+
/// </summary>
359+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
360+
/// <param name="parameterInfo">The <see cref="ParameterInfo" /> associated with the schema.</param>
361+
internal static void ApplyNullabilityContextInfo(this JsonNode schema, ParameterInfo parameterInfo)
362+
{
363+
if (parameterInfo.ParameterType.IsValueType)
364+
{
365+
return;
366+
}
367+
368+
var nullabilityInfo = _nullabilityInfoContext.Create(parameterInfo);
369+
if (nullabilityInfo.WriteState == NullabilityState.Nullable)
370+
{
371+
schema[OpenApiSchemaKeywords.NullableKeyword] = true;
372+
}
373+
}
374+
375+
/// <summary>
376+
/// Support applying nullability status for reference types provided as a property or field.
377+
/// </summary>
378+
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
379+
/// <param name="attributeProvider">The <see cref="PropertyInfo" /> or <see cref="FieldInfo"/> associated with the schema.</param>
380+
internal static void ApplyNullabilityContextInfo(this JsonNode schema, ICustomAttributeProvider attributeProvider)
381+
{
382+
var nullabilityInfo = attributeProvider switch
383+
{
384+
PropertyInfo propertyInfo => !propertyInfo.PropertyType.IsValueType ? _nullabilityInfoContext.Create(propertyInfo) : null,
385+
FieldInfo fieldInfo => !fieldInfo.FieldType.IsValueType ? _nullabilityInfoContext.Create(fieldInfo) : null,
386+
_ => null
387+
};
388+
if (nullabilityInfo is { WriteState: NullabilityState.Nullable } or { ReadState: NullabilityState.Nullable })
389+
{
390+
schema[OpenApiSchemaKeywords.NullableKeyword] = true;
391+
}
392+
}
338393
}

src/OpenApi/src/Schemas/JsonSchemaMapper/JsonSchemaGenerationContext.cs

Lines changed: 0 additions & 86 deletions
This file was deleted.

0 commit comments

Comments
 (0)