Skip to content

Commit 6b636d5

Browse files
authored
Merge pull request #2112 from microsoft/fix/nullable-down-cast-3-0
fix: a bug where 3.0 downcast of type null would not work
2 parents 1043e4e + 974ab44 commit 6b636d5

6 files changed

+56
-30
lines changed

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Text.Json;
78
using System.Text.Json.Nodes;
9+
using Microsoft.OpenApi.Any;
810
using Microsoft.OpenApi.Extensions;
911
using Microsoft.OpenApi.Helpers;
1012
using Microsoft.OpenApi.Interfaces;
@@ -356,12 +358,6 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
356358
// default
357359
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));
358360

359-
// nullable
360-
if (version is OpenApiSpecVersion.OpenApi3_0)
361-
{
362-
writer.WriteProperty(OpenApiConstants.Nullable, Nullable, false);
363-
}
364-
365361
// discriminator
366362
writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, callback);
367363

@@ -636,20 +632,39 @@ private void SerializeAsV2(
636632

637633
private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version)
638634
{
635+
// check whether nullable is true for upcasting purposes
636+
var isNullable = Nullable ||
637+
(Type.HasValue && Type.Value.HasFlag(JsonSchemaType.Null)) ||
638+
Extensions is not null &&
639+
Extensions.TryGetValue(OpenApiConstants.NullableExtension, out var nullExtRawValue) &&
640+
nullExtRawValue is OpenApiAny { Node: JsonNode jsonNode} &&
641+
jsonNode.GetValueKind() is JsonValueKind.True;
639642
if (type is null)
640643
{
641-
return;
642-
}
643-
if (!HasMultipleTypes(type.Value))
644-
{
645-
// check whether nullable is true for upcasting purposes
646-
if (version is OpenApiSpecVersion.OpenApi3_1 && (Nullable || Extensions.ContainsKey(OpenApiConstants.NullableExtension)))
644+
if (version is OpenApiSpecVersion.OpenApi3_0 && isNullable)
647645
{
648-
UpCastSchemaTypeToV31(type, writer);
646+
writer.WriteProperty(OpenApiConstants.Nullable, true);
649647
}
650-
else
648+
}
649+
else if (!HasMultipleTypes(type.Value))
650+
{
651+
652+
switch (version)
651653
{
652-
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier());
654+
case OpenApiSpecVersion.OpenApi3_1 when isNullable:
655+
UpCastSchemaTypeToV31(type.Value, writer);
656+
break;
657+
case OpenApiSpecVersion.OpenApi3_0 when isNullable && type.Value == JsonSchemaType.Null:
658+
writer.WriteProperty(OpenApiConstants.Nullable, true);
659+
writer.WriteProperty(OpenApiConstants.Type, JsonSchemaType.Object.ToIdentifier());
660+
break;
661+
case OpenApiSpecVersion.OpenApi3_0 when isNullable && type.Value != JsonSchemaType.Null:
662+
writer.WriteProperty(OpenApiConstants.Nullable, true);
663+
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier());
664+
break;
665+
default:
666+
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier());
667+
break;
653668
}
654669
}
655670
else
@@ -664,6 +679,10 @@ private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer,
664679
var list = (from JsonSchemaType flag in jsonSchemaTypeValues
665680
where type.Value.HasFlag(flag)
666681
select flag).ToList();
682+
if (Nullable && !list.Contains(JsonSchemaType.Null))
683+
{
684+
list.Add(JsonSchemaType.Null);
685+
}
667686
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier()));
668687
}
669688
}
@@ -681,14 +700,21 @@ private static bool HasMultipleTypes(JsonSchemaType schemaType)
681700
schemaTypeNumeric != (int)JsonSchemaType.Null;
682701
}
683702

684-
private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer)
703+
private static void UpCastSchemaTypeToV31(JsonSchemaType type, IOpenApiWriter writer)
685704
{
686705
// create a new array and insert the type and "null" as values
687-
Type = type | JsonSchemaType.Null;
688-
var list = (from JsonSchemaType? flag in jsonSchemaTypeValues// Check if the flag is set in 'type' using a bitwise AND operation
689-
where Type.Value.HasFlag(flag)
706+
var temporaryType = type | JsonSchemaType.Null;
707+
var list = (from JsonSchemaType flag in jsonSchemaTypeValues// Check if the flag is set in 'type' using a bitwise AND operation
708+
where temporaryType.HasFlag(flag)
690709
select flag.ToIdentifier()).ToList();
691-
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s));
710+
if (list.Count > 1)
711+
{
712+
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s));
713+
}
714+
else
715+
{
716+
writer.WriteProperty(OpenApiConstants.Type, list[0]);
717+
}
692718
}
693719

694720
#if NET5_0_OR_GREATER
@@ -711,7 +737,7 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
711737

712738
if (!HasMultipleTypes(schemaType ^ JsonSchemaType.Null) && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null
713739
{
714-
foreach (JsonSchemaType? flag in jsonSchemaTypeValues)
740+
foreach (JsonSchemaType flag in jsonSchemaTypeValues)
715741
{
716742
// Skip if the flag is not set or if it's the Null flag
717743
if (schemaType.HasFlag(flag) && flag != JsonSchemaType.Null)

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"maximum": 42,
55
"minimum": 10,
66
"exclusiveMinimum": true,
7+
"nullable": true,
78
"type": "integer",
89
"default": 15,
9-
"nullable": true,
1010
"externalDocs": {
1111
"url": "http://example.com/externalDocs"
1212
}
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"title":"title1","multipleOf":3,"maximum":42,"minimum":10,"exclusiveMinimum":true,"type":"integer","default":15,"nullable":true,"externalDocs":{"url":"http://example.com/externalDocs"}}
1+
{"title":"title1","multipleOf":3,"maximum":42,"minimum":10,"exclusiveMinimum":true,"nullable":true,"type":"integer","default":15,"externalDocs":{"url":"http://example.com/externalDocs"}}

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3WithoutReferenceJsonWorksAsync_produceTerseOutput=False.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"maximum": 42,
55
"minimum": 10,
66
"exclusiveMinimum": true,
7+
"nullable": true,
78
"type": "integer",
89
"default": 15,
9-
"nullable": true,
1010
"externalDocs": {
1111
"url": "http://example.com/externalDocs"
1212
}
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"title":"title1","multipleOf":3,"maximum":42,"minimum":10,"exclusiveMinimum":true,"type":"integer","default":15,"nullable":true,"externalDocs":{"url":"http://example.com/externalDocs"}}
1+
{"title":"title1","multipleOf":3,"maximum":42,"minimum":10,"exclusiveMinimum":true,"nullable":true,"type":"integer","default":15,"externalDocs":{"url":"http://example.com/externalDocs"}}

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ public async Task SerializeAdvancedSchemaNumberAsV3JsonWorks()
242242
"maximum": 42,
243243
"minimum": 10,
244244
"exclusiveMinimum": true,
245+
"nullable": true,
245246
"type": "integer",
246247
"default": 15,
247-
"nullable": true,
248248
"externalDocs": {
249249
"url": "http://example.com/externalDocs"
250250
}
@@ -268,6 +268,7 @@ public async Task SerializeAdvancedSchemaObjectAsV3JsonWorks()
268268
"""
269269
{
270270
"title": "title1",
271+
"nullable": true,
271272
"properties": {
272273
"property1": {
273274
"properties": {
@@ -296,7 +297,6 @@ public async Task SerializeAdvancedSchemaObjectAsV3JsonWorks()
296297
}
297298
}
298299
},
299-
"nullable": true,
300300
"externalDocs": {
301301
"url": "http://example.com/externalDocs"
302302
}
@@ -320,6 +320,7 @@ public async Task SerializeAdvancedSchemaWithAllOfAsV3JsonWorks()
320320
"""
321321
{
322322
"title": "title1",
323+
"nullable": true,
323324
"allOf": [
324325
{
325326
"title": "title2",
@@ -335,6 +336,7 @@ public async Task SerializeAdvancedSchemaWithAllOfAsV3JsonWorks()
335336
},
336337
{
337338
"title": "title3",
339+
"nullable": true,
338340
"properties": {
339341
"property3": {
340342
"properties": {
@@ -347,11 +349,9 @@ public async Task SerializeAdvancedSchemaWithAllOfAsV3JsonWorks()
347349
"minLength": 2,
348350
"type": "string"
349351
}
350-
},
351-
"nullable": true
352+
}
352353
}
353354
],
354-
"nullable": true,
355355
"externalDocs": {
356356
"url": "http://example.com/externalDocs"
357357
}

0 commit comments

Comments
 (0)