diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs b/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs index e6dadd44d..e47eff496 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs @@ -13,12 +13,27 @@ namespace Microsoft.OpenApi.Extensions /// public static class OpenApiTypeMapper { +#nullable enable /// /// Maps a JsonSchema data type to an identifier. /// /// /// - public static string ToIdentifier(this JsonSchemaType? schemaType) + public static string? ToIdentifier(this JsonSchemaType? schemaType) + { + if (schemaType is null) + { + return null; + } + return schemaType.Value.ToIdentifier(); + } + + /// + /// Maps a JsonSchema data type to an identifier. + /// + /// + /// + public static string? ToIdentifier(this JsonSchemaType schemaType) { return schemaType switch { @@ -32,6 +47,7 @@ public static string ToIdentifier(this JsonSchemaType? schemaType) _ => null, }; } +#nullable restore /// /// Converts a schema type's identifier into the enum equivalent diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 59b7e2025..c2456286c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -476,10 +476,7 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (nodeWriter, s) => nodeWriter.WriteAny(s)); // type - if (Type is not null) - { - SerializeTypeProperty(Type, writer, version); - } + SerializeTypeProperty(Type, writer, version); // allOf writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback); @@ -660,10 +657,7 @@ internal void SerializeAsV2( writer.WriteStartObject(); // type - if (Type is not null) - { - SerializeTypeProperty(Type, writer, OpenApiSpecVersion.OpenApi2_0); - } + SerializeTypeProperty(Type, writer, OpenApiSpecVersion.OpenApi2_0); // description writer.WriteProperty(OpenApiConstants.Description, Description); @@ -794,8 +788,11 @@ internal void SerializeAsV2( private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version) { - var flagsCount = CountEnumSetFlags(type); - if (flagsCount is 1) + if (type is null) + { + return; + } + if (!HasMultipleTypes(type.Value)) { // check whether nullable is true for upcasting purposes if (version is OpenApiSpecVersion.OpenApi3_1 && (Nullable || Extensions.ContainsKey(OpenApiConstants.NullableExtension))) @@ -804,53 +801,42 @@ private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, } else { - writer.WriteProperty(OpenApiConstants.Type, type.ToIdentifier()); + writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier()); } } - else if(flagsCount > 1) + else { // type if (version is OpenApiSpecVersion.OpenApi2_0 || version is OpenApiSpecVersion.OpenApi3_0) { - DowncastTypeArrayToV2OrV3(type, writer, version, flagsCount); + DowncastTypeArrayToV2OrV3(type.Value, writer, version); } else { - if (type is not null) + var list = new List(); + foreach (JsonSchemaType flag in jsonSchemaTypeValues) { - var list = new List(); - foreach (JsonSchemaType flag in System.Enum.GetValues(typeof(JsonSchemaType))) + if (type.Value.HasFlag(flag)) { - if (type.Value.HasFlag(flag)) - { - list.Add(flag); - } + list.Add(flag); } + } - writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier())); - } + writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier())); } } } - private static int CountEnumSetFlags(JsonSchemaType? schemaType) + private static bool IsPowerOfTwo(int x) { - int count = 0; - - if (schemaType != null) - { - // Check each flag in the enum - foreach (JsonSchemaType value in System.Enum.GetValues(typeof(JsonSchemaType))) - { - // Check if the flag is set - if (schemaType.Value.HasFlag(value)) - { - count++; - } - } - } + return x != 0 && (x & (x - 1)) == 0; + } - return count; + private static bool HasMultipleTypes(JsonSchemaType schemaType) + { + var schemaTypeNumeric = (int)schemaType; + return !IsPowerOfTwo(schemaTypeNumeric) && // Boolean, Integer, Number, String, Array, Object + schemaTypeNumeric != (int)JsonSchemaType.Null; } private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer) @@ -858,7 +844,7 @@ private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer) // create a new array and insert the type and "null" as values Type = type | JsonSchemaType.Null; var list = new List(); - foreach (JsonSchemaType? flag in System.Enum.GetValues(typeof(JsonSchemaType))) + foreach (JsonSchemaType? flag in jsonSchemaTypeValues) { // Check if the flag is set in 'type' using a bitwise AND operation if (Type.Value.HasFlag(flag)) @@ -870,7 +856,9 @@ private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer) writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s)); } - private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWriter writer, OpenApiSpecVersion version, int flagsCount) + private static readonly Array jsonSchemaTypeValues = System.Enum.GetValues(typeof(JsonSchemaType)); + + private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter writer, OpenApiSpecVersion version) { /* If the array has one non-null value, emit Type as string * If the array has one null value, emit x-nullable as true @@ -882,23 +870,12 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWrite ? OpenApiConstants.NullableExtension : OpenApiConstants.Nullable; - if (flagsCount is 1) + if (!HasMultipleTypes(schemaType ^ JsonSchemaType.Null) && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null { - if (schemaType is JsonSchemaType.Null) - { - writer.WriteProperty(nullableProp, true); - } - else - { - writer.WriteProperty(OpenApiConstants.Type, schemaType.ToIdentifier()); - } - } - else if (flagsCount is 2 && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null - { - foreach (JsonSchemaType? flag in System.Enum.GetValues(typeof(JsonSchemaType))) + foreach (JsonSchemaType? flag in jsonSchemaTypeValues) { // Skip if the flag is not set or if it's the Null flag - if (schemaType.Value.HasFlag(flag) && flag != JsonSchemaType.Null) + if (schemaType.HasFlag(flag) && flag != JsonSchemaType.Null) { // Write the non-null flag value to the writer writer.WriteProperty(OpenApiConstants.Type, flag.ToIdentifier()); @@ -909,6 +886,17 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWrite writer.WriteProperty(nullableProp, true); } } + else if (!HasMultipleTypes(schemaType)) + { + if (schemaType is JsonSchemaType.Null) + { + writer.WriteProperty(nullableProp, true); + } + else + { + writer.WriteProperty(OpenApiConstants.Type, schemaType.ToIdentifier()); + } + } } } } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index ef18b4cfb..3fe034011 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -192,7 +192,8 @@ namespace Microsoft.OpenApi.Extensions { public static System.Type MapOpenApiPrimitiveTypeToSimpleType(this Microsoft.OpenApi.Models.OpenApiSchema schema) { } public static Microsoft.OpenApi.Models.OpenApiSchema MapTypeToOpenApiPrimitiveType(this System.Type type) { } - public static string ToIdentifier(this Microsoft.OpenApi.Models.JsonSchemaType? schemaType) { } + public static string? ToIdentifier(this Microsoft.OpenApi.Models.JsonSchemaType schemaType) { } + public static string? ToIdentifier(this Microsoft.OpenApi.Models.JsonSchemaType? schemaType) { } public static Microsoft.OpenApi.Models.JsonSchemaType ToJsonSchemaType(this string identifier) { } } public static class StringExtensions