Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ private static void CopySchema(OpenApiSchema schema, OpenApiSchema newSchema)
schema.Enum ??= newSchema.Enum;
schema.ReadOnly = !schema.ReadOnly ? newSchema.ReadOnly : schema.ReadOnly;
schema.WriteOnly = !schema.WriteOnly ? newSchema.WriteOnly : schema.WriteOnly;
schema.Nullable = !schema.Nullable ? newSchema.Nullable : schema.Nullable;
schema.Deprecated = !schema.Deprecated ? newSchema.Deprecated : schema.Deprecated;
}
}
Expand Down
84 changes: 42 additions & 42 deletions src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@
return schemaType switch
{
JsonSchemaType.Null => "null",
JsonSchemaType.Boolean => "boolean",

Check warning on line 41 in src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'boolean' 4 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
JsonSchemaType.Integer => "integer",

Check warning on line 42 in src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'integer' 8 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
JsonSchemaType.Number => "number",

Check warning on line 43 in src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'number' 14 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
JsonSchemaType.String => "string",

Check warning on line 44 in src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'string' 13 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
JsonSchemaType.Array => "array",
JsonSchemaType.Object => "object",
_ => null,
Expand All @@ -61,7 +61,7 @@
"null" => JsonSchemaType.Null,
"boolean" => JsonSchemaType.Boolean,
"integer" or "int" => JsonSchemaType.Integer,
"number" or "double" or "float" or "decimal"=> JsonSchemaType.Number,

Check warning on line 64 in src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'double' 7 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)

Check warning on line 64 in src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'float' 5 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
"string" => JsonSchemaType.String,
"array" => JsonSchemaType.Array,
"object" => JsonSchemaType.Object,
Expand All @@ -87,19 +87,19 @@
[typeof(char)] = () => new() { Type = JsonSchemaType.String },

// Nullable types
[typeof(bool?)] = () => new() { Type = JsonSchemaType.Boolean, Nullable = true },
[typeof(byte?)] = () => new() { Type = JsonSchemaType.String, Format = "byte", Nullable = true },
[typeof(int?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true },
[typeof(uint?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true },
[typeof(long?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true },
[typeof(ulong?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true },
[typeof(float?)] = () => new() { Type = JsonSchemaType.Number, Format = "float", Nullable = true },
[typeof(double?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true },
[typeof(decimal?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true },
[typeof(DateTime?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true },
[typeof(DateTimeOffset?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true },
[typeof(Guid?)] = () => new() { Type = JsonSchemaType.String, Format = "uuid", Nullable = true },
[typeof(char?)] = () => new() { Type = JsonSchemaType.String, Nullable = true },
[typeof(bool?)] = () => new() { Type = JsonSchemaType.Boolean | JsonSchemaType.Null },
[typeof(byte?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "byte" },
[typeof(int?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int32" },
[typeof(uint?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int32" },
[typeof(long?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int64" },
[typeof(ulong?)] = () => new() { Type = JsonSchemaType.Integer | JsonSchemaType.Null, Format = "int64" },
[typeof(float?)] = () => new() { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "float" },
[typeof(double?)] = () => new() { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "double" },
[typeof(decimal?)] = () => new() { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "double" },
[typeof(DateTime?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "date-time" },
[typeof(DateTimeOffset?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "date-time" },
[typeof(Guid?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null, Format = "uuid" },
[typeof(char?)] = () => new() { Type = JsonSchemaType.String | JsonSchemaType.Null },

[typeof(Uri)] = () => new() { Type = JsonSchemaType.String, Format = "uri" }, // Uri is treated as simple string
[typeof(string)] = () => new() { Type = JsonSchemaType.String },
Expand Down Expand Up @@ -153,37 +153,37 @@
throw new ArgumentNullException(nameof(schema));
}

var type = (schema.Type.ToIdentifier(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch
var type = ((schema.Type & ~JsonSchemaType.Null).ToIdentifier(), schema.Format?.ToLowerInvariant(), schema.Type & JsonSchemaType.Null) switch
{
("boolean", null, false) => typeof(bool),
("integer" or "number", "int32", JsonSchemaType.Null) => typeof(int?),
("integer" or "number", "int64", JsonSchemaType.Null) => typeof(long?),
("integer", null, JsonSchemaType.Null) => typeof(long?),
("number", "float", JsonSchemaType.Null) => typeof(float?),
("number", "double", JsonSchemaType.Null) => typeof(double?),
("number", null, JsonSchemaType.Null) => typeof(double?),
("number", "decimal", JsonSchemaType.Null) => typeof(decimal?),
("string", "byte", JsonSchemaType.Null) => typeof(byte?),
("string", "date-time", JsonSchemaType.Null) => typeof(DateTimeOffset?),
("string", "uuid", JsonSchemaType.Null) => typeof(Guid?),
("string", "char", JsonSchemaType.Null) => typeof(char?),
("boolean", null, JsonSchemaType.Null) => typeof(bool?),
("boolean", null, _) => typeof(bool),
// integer is technically not valid with format, but we must provide some compatibility
("integer" or "number", "int32", false) => typeof(int),
("integer" or "number", "int64", false) => typeof(long),
("integer", null, false) => typeof(long),
("number", "float", false) => typeof(float),
("number", "double", false) => typeof(double),
("number", "decimal", false) => typeof(decimal),
("number", null, false) => typeof(double),
("string", "byte", false) => typeof(byte),
("string", "date-time", false) => typeof(DateTimeOffset),
("string", "uuid", false) => typeof(Guid),
("string", "duration", false) => typeof(TimeSpan),
("string", "char", false) => typeof(char),
("string", null, false) => typeof(string),
("object", null, false) => typeof(object),
("string", "uri", false) => typeof(Uri),
("integer" or "number", "int32", true) => typeof(int?),
("integer" or "number", "int64", true) => typeof(long?),
("integer", null, true) => typeof(long?),
("number", "float", true) => typeof(float?),
("number", "double", true) => typeof(double?),
("number", null, true) => typeof(double?),
("number", "decimal", true) => typeof(decimal?),
("string", "byte", true) => typeof(byte?),
("string", "date-time", true) => typeof(DateTimeOffset?),
("string", "uuid", true) => typeof(Guid?),
("string", "char", true) => typeof(char?),
("boolean", null, true) => typeof(bool?),
("integer" or "number", "int32", _) => typeof(int),
("integer" or "number", "int64", _) => typeof(long),
("integer", null, _) => typeof(long),
("number", "float", _) => typeof(float),
("number", "double", _) => typeof(double),
("number", "decimal", _) => typeof(decimal),
("number", null, _) => typeof(double),
("string", "byte", _) => typeof(byte),
("string", "date-time", _) => typeof(DateTimeOffset),
("string", "uuid", _) => typeof(Guid),
("string", "duration", _) => typeof(TimeSpan),
("string", "char", _) => typeof(char),
("string", null, _) => typeof(string),
("object", null, _) => typeof(object),
("string", "uri", _) => typeof(Uri),
_ => typeof(string),
};

Expand Down
5 changes: 0 additions & 5 deletions src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,6 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable
/// </summary>
public IList<JsonNode> Enum { get; }

/// <summary>
/// Allows sending a null value for the defined schema. Default value is false.
/// </summary>
public bool Nullable { get; }

/// <summary>
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
/// </summary>
Expand Down
18 changes: 3 additions & 15 deletions src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,6 @@ public class OpenApiSchema : IOpenApiReferenceable, IOpenApiExtensible, IOpenApi
/// <inheritdoc />
public IList<JsonNode> Enum { get; set; } = new List<JsonNode>();

/// <inheritdoc />
public bool Nullable { get; set; }

/// <inheritdoc />
public bool UnevaluatedProperties { get; set;}

Expand Down Expand Up @@ -236,7 +233,6 @@ internal OpenApiSchema(IOpenApiSchema schema)
Example = schema.Example != null ? JsonNodeCloneHelper.Clone(schema.Example) : null;
Examples = schema.Examples != null ? new List<JsonNode>(schema.Examples) : null;
Enum = schema.Enum != null ? new List<JsonNode>(schema.Enum) : null;
Nullable = schema.Nullable;
ExternalDocs = schema.ExternalDocs != null ? new(schema.ExternalDocs) : null;
Deprecated = schema.Deprecated;
Xml = schema.Xml != null ? new(schema.Xml) : null;
Expand Down Expand Up @@ -633,8 +629,7 @@ private void SerializeAsV2(
private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version)
{
// check whether nullable is true for upcasting purposes
var isNullable = Nullable ||
(Type.HasValue && Type.Value.HasFlag(JsonSchemaType.Null)) ||
var isNullable = (Type.HasValue && Type.Value.HasFlag(JsonSchemaType.Null)) ||
Extensions is not null &&
Extensions.TryGetValue(OpenApiConstants.NullableExtension, out var nullExtRawValue) &&
nullExtRawValue is OpenApiAny { Node: JsonNode jsonNode} &&
Expand Down Expand Up @@ -679,10 +674,6 @@ Extensions is not null &&
var list = (from JsonSchemaType flag in jsonSchemaTypeValues
where type.Value.HasFlag(flag)
select flag).ToList();
if (Nullable && !list.Contains(JsonSchemaType.Null))
{
list.Add(JsonSchemaType.Null);
}
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier()));
}
}
Expand Down Expand Up @@ -735,7 +726,7 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
? OpenApiConstants.NullableExtension
: OpenApiConstants.Nullable;

if (!HasMultipleTypes(schemaType ^ JsonSchemaType.Null) && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null
if (!HasMultipleTypes(schemaType & ~JsonSchemaType.Null) && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null
{
foreach (JsonSchemaType flag in jsonSchemaTypeValues)
{
Expand All @@ -746,10 +737,7 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
writer.WriteProperty(OpenApiConstants.Type, flag.ToIdentifier());
}
}
if (!Nullable)
{
writer.WriteProperty(nullableProp, true);
}
writer.WriteProperty(nullableProp, true);
}
else if (!HasMultipleTypes(schemaType))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,6 @@ public string Description
/// <inheritdoc/>
public IList<JsonNode> Enum { get => Target?.Enum; }
/// <inheritdoc/>
public bool Nullable { get => Target?.Nullable ?? false; }
/// <inheritdoc/>
public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? false; }
/// <inheritdoc/>
public OpenApiExternalDocs ExternalDocs { get => Target?.ExternalDocs; }
Expand Down
20 changes: 18 additions & 2 deletions src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,14 @@ internal static partial class OpenApiV3Deserializer
},
{
"type",
(o, n, _) => o.Type = n.GetScalarValue().ToJsonSchemaType()
(o, n, _) => {
var type = n.GetScalarValue().ToJsonSchemaType();
// so we don't loose the value from nullable
if (o.Type.HasValue)
o.Type |= type;
else
o.Type = type;
}
},
{
"allOf",
Expand Down Expand Up @@ -139,7 +146,16 @@ internal static partial class OpenApiV3Deserializer
},
{
"nullable",
(o, n, _) => o.Nullable = bool.Parse(n.GetScalarValue())
(o, n, _) =>
{
if (bool.TryParse(n.GetScalarValue(), out var parsed) && parsed)
{
if (o.Type.HasValue)
o.Type |= JsonSchemaType.Null;
else
o.Type = JsonSchemaType.Null;
}
}
},
{
"discriminator",
Expand Down
3 changes: 1 addition & 2 deletions src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ public static void ValidateDataTypeMismatch(

var type = schema.Type.ToIdentifier();
var format = schema.Format;
var nullable = schema.Nullable;

// Before checking the type, check first if the schema allows null.
// If so and the data given is also null, this is allowed for any type.
if (nullable && valueKind is JsonValueKind.Null)
if ((schema.Type.Value & JsonSchemaType.Null) is JsonSchemaType.Null && valueKind is JsonValueKind.Null)
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,23 @@ public void RemoveAnyOfAndOneOfFromSchema()
var walker = new OpenApiWalker(powerShellFormatter);
walker.Walk(openApiDocument);

var testSchema = openApiDocument.Components?.Schemas?["TestSchema"];
var averageAudioDegradationProperty = testSchema?.Properties["averageAudioDegradation"];
var defaultPriceProperty = testSchema?.Properties["defaultPrice"];
Assert.NotNull(openApiDocument.Components);
Assert.NotNull(openApiDocument.Components.Schemas);
var testSchema = openApiDocument.Components.Schemas["TestSchema"];
var averageAudioDegradationProperty = testSchema.Properties["averageAudioDegradation"];
var defaultPriceProperty = testSchema.Properties["defaultPrice"];

// Assert
Assert.NotNull(openApiDocument.Components);
Assert.NotNull(openApiDocument.Components.Schemas);
Assert.NotNull(testSchema);
Assert.Null(averageAudioDegradationProperty?.AnyOf);
Assert.Equal(JsonSchemaType.Number, averageAudioDegradationProperty?.Type);
Assert.Equal("float", averageAudioDegradationProperty?.Format);
Assert.True(averageAudioDegradationProperty?.Nullable);
Assert.Null(defaultPriceProperty?.OneOf);
Assert.Equal(JsonSchemaType.Number, defaultPriceProperty?.Type);
Assert.Equal("double", defaultPriceProperty?.Format);
Assert.Null(averageAudioDegradationProperty.AnyOf);
Assert.Equal(JsonSchemaType.Number | JsonSchemaType.Null, averageAudioDegradationProperty.Type);
Assert.Equal("float", averageAudioDegradationProperty.Format);
Assert.Equal(JsonSchemaType.Null, averageAudioDegradationProperty.Type & JsonSchemaType.Null);
Assert.Null(defaultPriceProperty.OneOf);
Assert.Equal(JsonSchemaType.Number, defaultPriceProperty.Type);
Assert.Equal("double", defaultPriceProperty.Format);
Assert.NotNull(testSchema.AdditionalProperties);
}

Expand Down Expand Up @@ -161,11 +163,10 @@ private static OpenApiDocument GetSampleOpenApiDocument()
{
AnyOf = new List<IOpenApiSchema>
{
new OpenApiSchema() { Type = JsonSchemaType.Number },
new OpenApiSchema() { Type = JsonSchemaType.Number | JsonSchemaType.Null },
new OpenApiSchema() { Type = JsonSchemaType.String }
},
Format = "float",
Nullable = true
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,14 +377,7 @@ public static OpenApiDocument CreateOpenApiDocument()
{
Schema = new OpenApiSchema()
{
AnyOf = new List<IOpenApiSchema>
{
new OpenApiSchema()
{
Type = JsonSchemaType.String
}
},
Nullable = true
Type = JsonSchemaType.String | JsonSchemaType.Null
}
}
}
Expand Down Expand Up @@ -627,9 +620,8 @@ public static OpenApiDocument CreateOpenApiDocument()
{
"description", new OpenApiSchema
{
Type = JsonSchemaType.String,
Type = JsonSchemaType.String | JsonSchemaType.Null,
Description = "Description of the NIC (e.g. Ethernet adapter, Wireless LAN adapter Local Area Connection <#>, etc.).",
Nullable = true
}
}
}
Expand Down
Loading
Loading