Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/OpenApi/sample/Endpoints/MapSchemasEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ public static IEndpointRouteBuilder MapSchemasEndpoints(this IEndpointRouteBuild

// Additional edge cases for nullable testing
schemas.MapPost("/nullable-array-elements", (NullableArrayModel model) => Results.Ok(model));

// Ensures that applying nullability to the elements of one array parameter does not
// affect the shared componentized schema referenced by the other array parameter.
schemas.MapGet("/nullable-and-non-nullable-array-elements", (TestEnum?[] nullableValues, TestEnum[] values) => { });
// Ensures the same componentized element type is reported correctly in a response.
schemas.MapGet("/complex-nullable-hierarchy", () => TypedResults.Ok(new ComplexHierarchyModel

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the case of testing a response, I think we should have a similar situation as the originally problematic one where the top-level type is nullable array. So something like schemas.MapGet("/response-array-with-nullable-element", () => TypedResults.Ok(Array.Empty<TestEnum>());

{
Id = "id",
RequiredNested = new NestedModel { Name = "nested" }
}));

schemas.MapGet("/optional-with-default", () => TypedResults.Ok(new ModelWithDefaults()));
schemas.MapGet("/nullable-enum-response", () => TypedResults.Ok(new EnumNullableModel
{
Expand Down
17 changes: 17 additions & 0 deletions src/OpenApi/src/Extensions/OpenApiSchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ public static IOpenApiSchema CreateOneOfNullableWrapper(this IOpenApiSchema orig
};
}

public static void MakeArrayItemsNullable(this IOpenApiSchema schema)
{
if (schema is not OpenApiSchema { Items: { } items } arraySchema)
{
return;
}

if (items is OpenApiSchema { Type: { } itemType } inlineItemSchema)
{
inlineItemSchema.Type = itemType | JsonSchemaType.Null;
}
else
{
arraySchema.Items = items.CreateOneOfNullableWrapper();
}
}

public static bool IsComponentizedSchema(this OpenApiSchema schema)
=> schema.IsComponentizedSchema(out _);

Expand Down
13 changes: 13 additions & 0 deletions src/OpenApi/src/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ public static bool ShouldApplyNullableRequestSchema(this ApiParameterDescription
return nullabilityInfo.WriteState == NullabilityState.Nullable;
}

public static bool ShouldApplyNullableArrayElementSchema(this ApiParameterDescription apiParameterDescription)
{
if (apiParameterDescription.Type is not { IsArray: true } ||
apiParameterDescription.ParameterDescriptor is not IParameterInfoParameterDescriptor { ParameterInfo: { } parameterInfo })
{
return false;
}

var nullabilityInfoContext = new NullabilityInfoContext();
var nullabilityInfo = nullabilityInfoContext.Create(parameterInfo);
return nullabilityInfo.ElementType?.WriteState == NullabilityState.Nullable;
}

public static bool ShouldApplyNullablePropertySchema(this JsonPropertyInfo jsonPropertyInfo)
{
if (jsonPropertyInfo.AttributeProvider is not PropertyInfo propertyInfo)
Expand Down
15 changes: 14 additions & 1 deletion src/OpenApi/src/Services/OpenApiDocumentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,19 @@ private static bool IsServerSentEventsContentType(string contentType)
continue;
}

var parameterSchema = await _componentService.GetOrCreateSchemaAsync(
Comment thread
Youssef1313 marked this conversation as resolved.
document,
GetTargetType(description, parameter),
scopedServiceProvider,
schemaTransformers,
parameter,
cancellationToken: cancellationToken);

if (parameter.ShouldApplyNullableArrayElementSchema())
{
parameterSchema.MakeArrayItemsNullable();
}

var openApiParameter = new OpenApiParameter
{
Name = parameter.Name,
Expand All @@ -599,7 +612,7 @@ private static bool IsServerSentEventsContentType(string contentType)
_ => ParameterLocation.Query
},
Required = IsRequired(parameter),
Schema = await _componentService.GetOrCreateSchemaAsync(document, GetTargetType(description, parameter), scopedServiceProvider, schemaTransformers, parameter, cancellationToken: cancellationToken),
Schema = parameterSchema,
Description = GetParameterDescriptionFromAttribute(parameter)
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,23 @@
"description": "OK"
}
}
},
"get": {
"tags": [
"Sample"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComplexHierarchyModel"
}
}
}
}
}
}
},
"/schemas-by-ref/nullable-array-elements": {
Expand All @@ -780,6 +797,51 @@
}
}
},
"/schemas-by-ref/nullable-and-non-nullable-array-elements": {
"get": {
"tags": [
"Sample"
],
"parameters": [
{
"name": "nullableValues",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"oneOf": [
{
"enum": [
null
]
},
{
"$ref": "#/components/schemas/TestEnum"
}
]
}
}
},
{
"name": "values",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TestEnum"
}
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/schemas-by-ref/optional-with-default": {
"get": {
"tags": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,23 @@
"description": "OK"
}
}
},
"get": {
"tags": [
"Sample"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComplexHierarchyModel"
}
}
}
}
}
}
},
"/schemas-by-ref/nullable-array-elements": {
Expand All @@ -776,6 +793,49 @@
}
}
},
"/schemas-by-ref/nullable-and-non-nullable-array-elements": {
"get": {
"tags": [
"Sample"
],
"parameters": [
{
"name": "nullableValues",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/TestEnum"
}
]
}
}
},
{
"name": "values",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TestEnum"
}
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/schemas-by-ref/optional-with-default": {
"get": {
"tags": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,23 @@
"description": "OK"
}
}
},
"get": {
"tags": [
"Sample"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComplexHierarchyModel"
}
}
}
}
}
}
},
"/schemas-by-ref/nullable-array-elements": {
Expand All @@ -776,6 +793,49 @@
}
}
},
"/schemas-by-ref/nullable-and-non-nullable-array-elements": {
"get": {
"tags": [
"Sample"
],
"parameters": [
{
"name": "nullableValues",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/TestEnum"
}
]
}
}
},
{
"name": "values",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TestEnum"
}
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/schemas-by-ref/optional-with-default": {
"get": {
"tags": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,23 @@
"description": "OK"
}
}
},
"get": {
"tags": [
"Sample"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComplexHierarchyModel"
}
}
}
}
}
}
},
"/schemas-by-ref/nullable-array-elements": {
Expand All @@ -1771,6 +1788,49 @@
}
}
},
"/schemas-by-ref/nullable-and-non-nullable-array-elements": {
"get": {
"tags": [
"Sample"
],
"parameters": [
{
"name": "nullableValues",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/TestEnum"
}
]
}
}
},
{
"name": "values",
"in": "query",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TestEnum"
}
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/schemas-by-ref/optional-with-default": {
"get": {
"tags": [
Expand Down
Loading
Loading