Skip to content

Commit ac05342

Browse files
committed
fix: a bug where 3.0 downcast of type null would not work
1 parent c637cc4 commit ac05342

6 files changed

+40
-27
lines changed

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 31 additions & 18 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;
@@ -355,12 +357,6 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
355357
// default
356358
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));
357359

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

@@ -635,20 +631,33 @@ private void SerializeAsV2(
635631

636632
private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version)
637633
{
634+
// check whether nullable is true for upcasting purposes
635+
var isNullable = Nullable ||
636+
Extensions.TryGetValue(OpenApiConstants.NullableExtension, out var nullExtRawValue) &&
637+
nullExtRawValue is OpenApiAny openApiAny &&
638+
openApiAny.Node is JsonNode jsonNode &&
639+
jsonNode.GetValueKind() is JsonValueKind.True;
638640
if (type is null)
639641
{
640-
return;
641-
}
642-
if (!HasMultipleTypes(type.Value))
643-
{
644-
// check whether nullable is true for upcasting purposes
645-
if (version is OpenApiSpecVersion.OpenApi3_1 && (Nullable || Extensions.ContainsKey(OpenApiConstants.NullableExtension)))
642+
if (version is OpenApiSpecVersion.OpenApi3_0 && isNullable)
646643
{
647-
UpCastSchemaTypeToV31(type, writer);
644+
writer.WriteProperty(OpenApiConstants.Nullable, true);
648645
}
649-
else
646+
}
647+
else if (!HasMultipleTypes(type.Value))
648+
{
649+
650+
switch (version)
650651
{
651-
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier());
652+
case OpenApiSpecVersion.OpenApi3_1 when isNullable:
653+
UpCastSchemaTypeToV31(type.Value, writer);
654+
break;
655+
case OpenApiSpecVersion.OpenApi3_0 when isNullable:
656+
writer.WriteProperty(OpenApiConstants.Nullable, true);
657+
goto default;
658+
default:
659+
writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier());
660+
break;
652661
}
653662
}
654663
else
@@ -663,6 +672,10 @@ private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer,
663672
var list = (from JsonSchemaType flag in jsonSchemaTypeValues
664673
where type.Value.HasFlag(flag)
665674
select flag).ToList();
675+
if (Nullable && !list.Contains(JsonSchemaType.Null))
676+
{
677+
list.Add(JsonSchemaType.Null);
678+
}
666679
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier()));
667680
}
668681
}
@@ -680,12 +693,12 @@ private static bool HasMultipleTypes(JsonSchemaType schemaType)
680693
schemaTypeNumeric != (int)JsonSchemaType.Null;
681694
}
682695

683-
private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer)
696+
private void UpCastSchemaTypeToV31(JsonSchemaType type, IOpenApiWriter writer)
684697
{
685698
// create a new array and insert the type and "null" as values
686-
Type = type | JsonSchemaType.Null;
699+
var temporaryType = type | JsonSchemaType.Null;
687700
var list = (from JsonSchemaType? flag in jsonSchemaTypeValues// Check if the flag is set in 'type' using a bitwise AND operation
688-
where Type.Value.HasFlag(flag)
701+
where temporaryType.HasFlag(flag)
689702
select flag.ToIdentifier()).ToList();
690703
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s));
691704
}

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)