Skip to content

Commit 4821b92

Browse files
authored
Merge pull request #2117 from microsoft/fix/no-more-nullable-property
fix/no more nullable property
2 parents 6b636d5 + 2f171a3 commit 4821b92

18 files changed

+127
-145
lines changed

src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs

-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ private static void CopySchema(OpenApiSchema schema, OpenApiSchema newSchema)
243243
schema.Enum ??= newSchema.Enum;
244244
schema.ReadOnly = !schema.ReadOnly ? newSchema.ReadOnly : schema.ReadOnly;
245245
schema.WriteOnly = !schema.WriteOnly ? newSchema.WriteOnly : schema.WriteOnly;
246-
schema.Nullable = !schema.Nullable ? newSchema.Nullable : schema.Nullable;
247246
schema.Deprecated = !schema.Deprecated ? newSchema.Deprecated : schema.Deprecated;
248247
}
249248
}

src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

+42-42
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,19 @@ public static JsonSchemaType ToJsonSchemaType(this string identifier)
8787
[typeof(char)] = () => new() { Type = JsonSchemaType.String },
8888

8989
// Nullable types
90-
[typeof(bool?)] = () => new() { Type = JsonSchemaType.Boolean, Nullable = true },
91-
[typeof(byte?)] = () => new() { Type = JsonSchemaType.String, Format = "byte", Nullable = true },
92-
[typeof(int?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true },
93-
[typeof(uint?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true },
94-
[typeof(long?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true },
95-
[typeof(ulong?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true },
96-
[typeof(float?)] = () => new() { Type = JsonSchemaType.Number, Format = "float", Nullable = true },
97-
[typeof(double?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true },
98-
[typeof(decimal?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true },
99-
[typeof(DateTime?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true },
100-
[typeof(DateTimeOffset?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true },
101-
[typeof(Guid?)] = () => new() { Type = JsonSchemaType.String, Format = "uuid", Nullable = true },
102-
[typeof(char?)] = () => new() { Type = JsonSchemaType.String, Nullable = true },
90+
[typeof(bool?)] = () => new() { Type = JsonSchemaType.Boolean | JsonSchemaType.Null },
91+
[typeof(byte?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "byte" },
92+
[typeof(int?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int32" },
93+
[typeof(uint?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int32" },
94+
[typeof(long?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int64" },
95+
[typeof(ulong?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int64" },
96+
[typeof(float?)] = () => new() { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "float" },
97+
[typeof(double?)] = () => new() { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "double" },
98+
[typeof(decimal?)] = () => new() { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "double" },
99+
[typeof(DateTime?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "date-time" },
100+
[typeof(DateTimeOffset?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "date-time" },
101+
[typeof(Guid?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "uuid" },
102+
[typeof(char?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null },
103103

104104
[typeof(Uri)] = () => new() { Type = JsonSchemaType.String, Format = "uri" }, // Uri is treated as simple string
105105
[typeof(string)] = () => new() { Type = JsonSchemaType.String },
@@ -153,37 +153,37 @@ public static Type MapOpenApiPrimitiveTypeToSimpleType(this OpenApiSchema schema
153153
throw new ArgumentNullException(nameof(schema));
154154
}
155155

156-
var type = (schema.Type.ToIdentifier(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch
156+
var type = ((schema.Type & ~JsonSchemaType.Null).ToIdentifier(), schema.Format?.ToLowerInvariant(), schema.Type & JsonSchemaType.Null) switch
157157
{
158-
("boolean", null, false) => typeof(bool),
158+
("integer" or "number", "int32", JsonSchemaType.Null) => typeof(int?),
159+
("integer" or "number", "int64", JsonSchemaType.Null) => typeof(long?),
160+
("integer", null, JsonSchemaType.Null) => typeof(long?),
161+
("number", "float", JsonSchemaType.Null) => typeof(float?),
162+
("number", "double", JsonSchemaType.Null) => typeof(double?),
163+
("number", null, JsonSchemaType.Null) => typeof(double?),
164+
("number", "decimal", JsonSchemaType.Null) => typeof(decimal?),
165+
("string", "byte", JsonSchemaType.Null) => typeof(byte?),
166+
("string", "date-time", JsonSchemaType.Null) => typeof(DateTimeOffset?),
167+
("string", "uuid", JsonSchemaType.Null) => typeof(Guid?),
168+
("string", "char", JsonSchemaType.Null) => typeof(char?),
169+
("boolean", null, JsonSchemaType.Null) => typeof(bool?),
170+
("boolean", null, _) => typeof(bool),
159171
// integer is technically not valid with format, but we must provide some compatibility
160-
("integer" or "number", "int32", false) => typeof(int),
161-
("integer" or "number", "int64", false) => typeof(long),
162-
("integer", null, false) => typeof(long),
163-
("number", "float", false) => typeof(float),
164-
("number", "double", false) => typeof(double),
165-
("number", "decimal", false) => typeof(decimal),
166-
("number", null, false) => typeof(double),
167-
("string", "byte", false) => typeof(byte),
168-
("string", "date-time", false) => typeof(DateTimeOffset),
169-
("string", "uuid", false) => typeof(Guid),
170-
("string", "duration", false) => typeof(TimeSpan),
171-
("string", "char", false) => typeof(char),
172-
("string", null, false) => typeof(string),
173-
("object", null, false) => typeof(object),
174-
("string", "uri", false) => typeof(Uri),
175-
("integer" or "number", "int32", true) => typeof(int?),
176-
("integer" or "number", "int64", true) => typeof(long?),
177-
("integer", null, true) => typeof(long?),
178-
("number", "float", true) => typeof(float?),
179-
("number", "double", true) => typeof(double?),
180-
("number", null, true) => typeof(double?),
181-
("number", "decimal", true) => typeof(decimal?),
182-
("string", "byte", true) => typeof(byte?),
183-
("string", "date-time", true) => typeof(DateTimeOffset?),
184-
("string", "uuid", true) => typeof(Guid?),
185-
("string", "char", true) => typeof(char?),
186-
("boolean", null, true) => typeof(bool?),
172+
("integer" or "number", "int32", _) => typeof(int),
173+
("integer" or "number", "int64", _) => typeof(long),
174+
("integer", null, _) => typeof(long),
175+
("number", "float", _) => typeof(float),
176+
("number", "double", _) => typeof(double),
177+
("number", "decimal", _) => typeof(decimal),
178+
("number", null, _) => typeof(double),
179+
("string", "byte", _) => typeof(byte),
180+
("string", "date-time", _) => typeof(DateTimeOffset),
181+
("string", "uuid", _) => typeof(Guid),
182+
("string", "duration", _) => typeof(TimeSpan),
183+
("string", "char", _) => typeof(char),
184+
("string", null, _) => typeof(string),
185+
("object", null, _) => typeof(object),
186+
("string", "uri", _) => typeof(Uri),
187187
_ => typeof(string),
188188
};
189189

src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs

-5
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,6 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable
267267
/// </summary>
268268
public IList<JsonNode> Enum { get; }
269269

270-
/// <summary>
271-
/// Allows sending a null value for the defined schema. Default value is false.
272-
/// </summary>
273-
public bool Nullable { get; }
274-
275270
/// <summary>
276271
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
277272
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

+3-15
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,6 @@ public class OpenApiSchema : IOpenApiReferenceable, IOpenApiExtensible, IOpenApi
155155
/// <inheritdoc />
156156
public IList<JsonNode> Enum { get; set; } = new List<JsonNode>();
157157

158-
/// <inheritdoc />
159-
public bool Nullable { get; set; }
160-
161158
/// <inheritdoc />
162159
public bool UnevaluatedProperties { get; set;}
163160

@@ -236,7 +233,6 @@ internal OpenApiSchema(IOpenApiSchema schema)
236233
Example = schema.Example != null ? JsonNodeCloneHelper.Clone(schema.Example) : null;
237234
Examples = schema.Examples != null ? new List<JsonNode>(schema.Examples) : null;
238235
Enum = schema.Enum != null ? new List<JsonNode>(schema.Enum) : null;
239-
Nullable = schema.Nullable;
240236
ExternalDocs = schema.ExternalDocs != null ? new(schema.ExternalDocs) : null;
241237
Deprecated = schema.Deprecated;
242238
Xml = schema.Xml != null ? new(schema.Xml) : null;
@@ -633,8 +629,7 @@ private void SerializeAsV2(
633629
private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version)
634630
{
635631
// check whether nullable is true for upcasting purposes
636-
var isNullable = Nullable ||
637-
(Type.HasValue && Type.Value.HasFlag(JsonSchemaType.Null)) ||
632+
var isNullable = (Type.HasValue && Type.Value.HasFlag(JsonSchemaType.Null)) ||
638633
Extensions is not null &&
639634
Extensions.TryGetValue(OpenApiConstants.NullableExtension, out var nullExtRawValue) &&
640635
nullExtRawValue is OpenApiAny { Node: JsonNode jsonNode} &&
@@ -679,10 +674,6 @@ Extensions is not null &&
679674
var list = (from JsonSchemaType flag in jsonSchemaTypeValues
680675
where type.Value.HasFlag(flag)
681676
select flag).ToList();
682-
if (Nullable && !list.Contains(JsonSchemaType.Null))
683-
{
684-
list.Add(JsonSchemaType.Null);
685-
}
686677
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier()));
687678
}
688679
}
@@ -735,7 +726,7 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
735726
? OpenApiConstants.NullableExtension
736727
: OpenApiConstants.Nullable;
737728

738-
if (!HasMultipleTypes(schemaType ^ JsonSchemaType.Null) && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null
729+
if (!HasMultipleTypes(schemaType & ~JsonSchemaType.Null) && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null
739730
{
740731
foreach (JsonSchemaType flag in jsonSchemaTypeValues)
741732
{
@@ -746,10 +737,7 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
746737
writer.WriteProperty(OpenApiConstants.Type, flag.ToIdentifier());
747738
}
748739
}
749-
if (!Nullable)
750-
{
751-
writer.WriteProperty(nullableProp, true);
752-
}
740+
writer.WriteProperty(nullableProp, true);
753741
}
754742
else if (!HasMultipleTypes(schemaType))
755743
{

src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs

-2
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,6 @@ public string Description
142142
/// <inheritdoc/>
143143
public IList<JsonNode> Enum { get => Target?.Enum; }
144144
/// <inheritdoc/>
145-
public bool Nullable { get => Target?.Nullable ?? false; }
146-
/// <inheritdoc/>
147145
public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? false; }
148146
/// <inheritdoc/>
149147
public OpenApiExternalDocs ExternalDocs { get => Target?.ExternalDocs; }

src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs

+18-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ internal static partial class OpenApiV3Deserializer
8686
},
8787
{
8888
"type",
89-
(o, n, _) => o.Type = n.GetScalarValue().ToJsonSchemaType()
89+
(o, n, _) => {
90+
var type = n.GetScalarValue().ToJsonSchemaType();
91+
// so we don't loose the value from nullable
92+
if (o.Type.HasValue)
93+
o.Type |= type;
94+
else
95+
o.Type = type;
96+
}
9097
},
9198
{
9299
"allOf",
@@ -139,7 +146,16 @@ internal static partial class OpenApiV3Deserializer
139146
},
140147
{
141148
"nullable",
142-
(o, n, _) => o.Nullable = bool.Parse(n.GetScalarValue())
149+
(o, n, _) =>
150+
{
151+
if (bool.TryParse(n.GetScalarValue(), out var parsed) && parsed)
152+
{
153+
if (o.Type.HasValue)
154+
o.Type |= JsonSchemaType.Null;
155+
else
156+
o.Type = JsonSchemaType.Null;
157+
}
158+
}
143159
},
144160
{
145161
"discriminator",

src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,10 @@ public static void ValidateDataTypeMismatch(
5757

5858
var type = schema.Type.ToIdentifier();
5959
var format = schema.Format;
60-
var nullable = schema.Nullable;
6160

6261
// Before checking the type, check first if the schema allows null.
6362
// If so and the data given is also null, this is allowed for any type.
64-
if (nullable && valueKind is JsonValueKind.Null)
63+
if ((schema.Type.Value & JsonSchemaType.Null) is JsonSchemaType.Null && valueKind is JsonValueKind.Null)
6564
{
6665
return;
6766
}

test/Microsoft.OpenApi.Hidi.Tests/Formatters/PowerShellFormatterTests.cs

+13-12
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,23 @@ public void RemoveAnyOfAndOneOfFromSchema()
5858
var walker = new OpenApiWalker(powerShellFormatter);
5959
walker.Walk(openApiDocument);
6060

61-
var testSchema = openApiDocument.Components?.Schemas?["TestSchema"];
62-
var averageAudioDegradationProperty = testSchema?.Properties["averageAudioDegradation"];
63-
var defaultPriceProperty = testSchema?.Properties["defaultPrice"];
61+
Assert.NotNull(openApiDocument.Components);
62+
Assert.NotNull(openApiDocument.Components.Schemas);
63+
var testSchema = openApiDocument.Components.Schemas["TestSchema"];
64+
var averageAudioDegradationProperty = testSchema.Properties["averageAudioDegradation"];
65+
var defaultPriceProperty = testSchema.Properties["defaultPrice"];
6466

6567
// Assert
6668
Assert.NotNull(openApiDocument.Components);
6769
Assert.NotNull(openApiDocument.Components.Schemas);
6870
Assert.NotNull(testSchema);
69-
Assert.Null(averageAudioDegradationProperty?.AnyOf);
70-
Assert.Equal(JsonSchemaType.Number, averageAudioDegradationProperty?.Type);
71-
Assert.Equal("float", averageAudioDegradationProperty?.Format);
72-
Assert.True(averageAudioDegradationProperty?.Nullable);
73-
Assert.Null(defaultPriceProperty?.OneOf);
74-
Assert.Equal(JsonSchemaType.Number, defaultPriceProperty?.Type);
75-
Assert.Equal("double", defaultPriceProperty?.Format);
71+
Assert.Null(averageAudioDegradationProperty.AnyOf);
72+
Assert.Equal(JsonSchemaType.Number | JsonSchemaType.Null, averageAudioDegradationProperty.Type);
73+
Assert.Equal("float", averageAudioDegradationProperty.Format);
74+
Assert.Equal(JsonSchemaType.Null, averageAudioDegradationProperty.Type & JsonSchemaType.Null);
75+
Assert.Null(defaultPriceProperty.OneOf);
76+
Assert.Equal(JsonSchemaType.Number, defaultPriceProperty.Type);
77+
Assert.Equal("double", defaultPriceProperty.Format);
7678
Assert.NotNull(testSchema.AdditionalProperties);
7779
}
7880

@@ -161,11 +163,10 @@ private static OpenApiDocument GetSampleOpenApiDocument()
161163
{
162164
AnyOf = new List<IOpenApiSchema>
163165
{
164-
new OpenApiSchema() { Type = JsonSchemaType.Number },
166+
new OpenApiSchema() { Type = JsonSchemaType.Number | JsonSchemaType.Null },
165167
new OpenApiSchema() { Type = JsonSchemaType.String }
166168
},
167169
Format = "float",
168-
Nullable = true
169170
}
170171
},
171172
{

test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs

+2-10
Original file line numberDiff line numberDiff line change
@@ -377,14 +377,7 @@ public static OpenApiDocument CreateOpenApiDocument()
377377
{
378378
Schema = new OpenApiSchema()
379379
{
380-
AnyOf = new List<IOpenApiSchema>
381-
{
382-
new OpenApiSchema()
383-
{
384-
Type = JsonSchemaType.String
385-
}
386-
},
387-
Nullable = true
380+
Type = JsonSchemaType.String | JsonSchemaType.Null
388381
}
389382
}
390383
}
@@ -627,9 +620,8 @@ public static OpenApiDocument CreateOpenApiDocument()
627620
{
628621
"description", new OpenApiSchema
629622
{
630-
Type = JsonSchemaType.String,
623+
Type = JsonSchemaType.String | JsonSchemaType.Null,
631624
Description = "Description of the NIC (e.g. Ethernet adapter, Wireless LAN adapter Local Area Connection <#>, etc.).",
632-
Nullable = true
633625
}
634626
}
635627
}

0 commit comments

Comments
 (0)