Skip to content

Commit 79f3e94

Browse files
committed
Define JSON schema type as a flaggable enum to allow storing strings or array of strings
1 parent c3373af commit 79f3e94

File tree

6 files changed

+126
-54
lines changed

6 files changed

+126
-54
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ private static IList<OpenApiParameter> ResolveFunctionParameters(IList<OpenApiPa
166166
parameter.Content = null;
167167
parameter.Schema = new()
168168
{
169-
Type = "array",
169+
Type = JsonSchemaType.Array,
170170
Items = new()
171171
{
172-
Type = "string"
172+
Type = JsonSchemaType.String
173173
}
174174
};
175175
}
@@ -178,9 +178,9 @@ private static IList<OpenApiParameter> ResolveFunctionParameters(IList<OpenApiPa
178178

179179
private void AddAdditionalPropertiesToSchema(OpenApiSchema schema)
180180
{
181-
if (schema != null && !_schemaLoop.Contains(schema) && "object".Equals((string)schema.Type, StringComparison.OrdinalIgnoreCase))
181+
if (schema != null && !_schemaLoop.Contains(schema) && schema.Type.Equals(JsonSchemaType.Object))
182182
{
183-
schema.AdditionalProperties = new() { Type = "object" };
183+
schema.AdditionalProperties = new() { Type = JsonSchemaType.Object };
184184

185185
/* Because 'additionalProperties' are now being walked,
186186
* we need a way to keep track of visited schemas to avoid
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.OpenApi.Models
7+
{
8+
/// <summary>
9+
/// Represents the type of a JSON schema.
10+
/// </summary>
11+
[Flags]
12+
public enum JsonSchemaType
13+
{
14+
/// <summary>
15+
/// Represents any type.
16+
/// </summary>
17+
Any = 0,
18+
19+
/// <summary>
20+
/// Represents a null type.
21+
/// </summary>
22+
Null = 1,
23+
24+
/// <summary>
25+
/// Represents a boolean type.
26+
/// </summary>
27+
Boolean = 2,
28+
29+
/// <summary>
30+
/// Represents an integer type.
31+
/// </summary>
32+
Integer = 4,
33+
34+
/// <summary>
35+
/// Represents a number type.
36+
/// </summary>
37+
Number = 8,
38+
39+
/// <summary>
40+
/// Represents a string type.
41+
/// </summary>
42+
String = 16,
43+
44+
/// <summary>
45+
/// Represents an object type.
46+
/// </summary>
47+
Object = 32,
48+
49+
/// <summary>
50+
/// Represents an array type.
51+
/// </summary>
52+
Array = 64,
53+
}
54+
}

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System;
@@ -292,7 +292,7 @@ public virtual void SerializeAsV2(IOpenApiWriter writer)
292292
}
293293
// In V2 parameter's type can't be a reference to a custom object schema or can't be of type object
294294
// So in that case map the type as string.
295-
else if (Schema?.UnresolvedReference == true || "object".Equals(Schema?.Type?.ToString(), StringComparison.OrdinalIgnoreCase))
295+
else if (Schema?.UnresolvedReference == true || Schema?.Type == JsonSchemaType.Object)
296296
{
297297
writer.WriteProperty(OpenApiConstants.Type, "string");
298298
}
@@ -333,7 +333,7 @@ public virtual void SerializeAsV2(IOpenApiWriter writer)
333333
// allowEmptyValue
334334
writer.WriteProperty(OpenApiConstants.AllowEmptyValue, AllowEmptyValue, false);
335335

336-
if (this.In == ParameterLocation.Query && "array".Equals(Schema?.Type.ToString(), StringComparison.OrdinalIgnoreCase))
336+
if (this.In == ParameterLocation.Query && Schema?.Type == JsonSchemaType.Array)
337337
{
338338
if (this.Style == ParameterStyle.Form && this.Explode == true)
339339
{

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Text.Json.Nodes;
8+
using Microsoft.OpenApi.Extensions;
89
using Microsoft.OpenApi.Helpers;
910
using Microsoft.OpenApi.Interfaces;
1011
using Microsoft.OpenApi.Writers;
@@ -90,7 +91,7 @@ public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiRe
9091
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
9192
/// Value MUST be a string in V2 and V3.
9293
/// </summary>
93-
public virtual object Type { get; set; }
94+
public virtual JsonSchemaType? Type { get; set; }
9495

9596
/// <summary>
9697
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
@@ -367,7 +368,7 @@ public OpenApiSchema(OpenApiSchema schema)
367368
UnevaluatedProperties = schema?.UnevaluatedProperties ?? UnevaluatedProperties;
368369
V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum;
369370
V31ExclusiveMinimum = schema?.V31ExclusiveMinimum ?? V31ExclusiveMinimum;
370-
Type = DeepCloneType(schema?.Type);
371+
Type = schema?.Type ?? Type;
371372
Format = schema?.Format ?? Format;
372373
Description = schema?.Description ?? Description;
373374
Maximum = schema?.Maximum ?? Maximum;
@@ -590,7 +591,7 @@ internal void WriteV31Properties(IOpenApiWriter writer)
590591
internal void WriteAsItemsProperties(IOpenApiWriter writer)
591592
{
592593
// type
593-
writer.WriteProperty(OpenApiConstants.Type, (string)Type);
594+
writer.WriteProperty(OpenApiConstants.Type, OpenApiTypeMapper.ToIdentifier(Type));
594595

595596
// format
596597
if (string.IsNullOrEmpty(Format))
@@ -670,14 +671,7 @@ internal void SerializeAsV2(
670671
writer.WriteStartObject();
671672

672673
// type
673-
if (Type is string[] array)
674-
{
675-
DowncastTypeArrayToV2OrV3(array, writer, OpenApiSpecVersion.OpenApi2_0);
676-
}
677-
else
678-
{
679-
writer.WriteProperty(OpenApiConstants.Type, (string)Type);
680-
}
674+
SerializeTypeProperty(Type, writer, OpenApiSpecVersion.OpenApi2_0);
681675

682676
// description
683677
writer.WriteProperty(OpenApiConstants.Description, Description);
@@ -806,88 +800,112 @@ internal void SerializeAsV2(
806800
writer.WriteEndObject();
807801
}
808802

809-
private void SerializeTypeProperty(object type, IOpenApiWriter writer, OpenApiSpecVersion version)
803+
private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version)
810804
{
811-
if (type?.GetType() == typeof(string))
805+
var flagsCount = CountEnumSetFlags(type);
806+
if (flagsCount is 1)
812807
{
813808
// check whether nullable is true for upcasting purposes
814-
if (Nullable || Extensions.ContainsKey(OpenApiConstants.NullableExtension))
809+
if (version is OpenApiSpecVersion.OpenApi3_1 && (Nullable || Extensions.ContainsKey(OpenApiConstants.NullableExtension)))
815810
{
816-
// create a new array and insert the type and "null" as values
817-
Type = new[] { (string)Type, OpenApiConstants.Null };
811+
UpCastSchemaTypeToV31(type, writer);
818812
}
819813
else
820814
{
821-
writer.WriteProperty(OpenApiConstants.Type, (string)Type);
815+
writer.WriteProperty(OpenApiConstants.Type, OpenApiTypeMapper.ToIdentifier(type));
822816
}
823817
}
824-
if (Type is string[] array)
818+
else if(flagsCount > 1)
825819
{
826820
// type
827-
if (version is OpenApiSpecVersion.OpenApi3_0)
821+
if (version is OpenApiSpecVersion.OpenApi2_0 || version is OpenApiSpecVersion.OpenApi3_0)
828822
{
829-
DowncastTypeArrayToV2OrV3(array, writer, OpenApiSpecVersion.OpenApi3_0);
823+
DowncastTypeArrayToV2OrV3(type, writer, version, flagsCount);
830824
}
831825
else
832826
{
833-
writer.WriteOptionalCollection(OpenApiConstants.Type, (string[])Type, (w, s) => w.WriteRaw(s));
827+
var list = new List<JsonSchemaType>();
828+
foreach (JsonSchemaType flag in System.Enum.GetValues(typeof(JsonSchemaType)))
829+
{
830+
list.Add(flag);
831+
}
832+
833+
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteRaw(OpenApiTypeMapper.ToIdentifier(s)));
834834
}
835835
}
836836
}
837837

838-
private object DeepCloneType(object type)
838+
private static int CountEnumSetFlags(JsonSchemaType? schemaType)
839839
{
840-
if (type == null)
841-
return null;
840+
int count = 0;
842841

843-
if (type is string)
842+
if(schemaType != null)
844843
{
845-
return type; // Return the string as is
846-
}
844+
// Check each flag in the enum
845+
foreach (JsonSchemaType value in System.Enum.GetValues(typeof(JsonSchemaType)))
846+
{
847+
// Ignore the None flag and check if the flag is set
848+
if (value != JsonSchemaType.Any && (schemaType & value) == value)
849+
{
850+
count++;
851+
}
852+
}
853+
}
854+
855+
return count;
856+
}
847857

848-
if (type is Array array)
858+
private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer)
859+
{
860+
// create a new array and insert the type and "null" as values
861+
Type = type | JsonSchemaType.Null;
862+
var list = new List<string>();
863+
foreach (JsonSchemaType flag in System.Enum.GetValues(typeof(JsonSchemaType)))
849864
{
850-
Type elementType = type.GetType().GetElementType();
851-
Array copiedArray = Array.CreateInstance(elementType, array.Length);
852-
for (int i = 0; i < array?.Length; i++)
865+
// Check if the flag is set in 'type' using a bitwise AND operation
866+
if ((Type & flag) == flag && flag != JsonSchemaType.Any)
853867
{
854-
copiedArray.SetValue(DeepCloneType(array?.GetValue(i)), i);
868+
list.Add(OpenApiTypeMapper.ToIdentifier(flag));
855869
}
856-
return copiedArray;
857870
}
858871

859-
return null;
872+
writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteRaw(s));
860873
}
861874

862-
private void DowncastTypeArrayToV2OrV3(string[] array, IOpenApiWriter writer, OpenApiSpecVersion version)
875+
private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWriter writer, OpenApiSpecVersion version, int flagsCount)
863876
{
864877
/* If the array has one non-null value, emit Type as string
865878
* If the array has one null value, emit x-nullable as true
866879
* If the array has two values, one null and one non-null, emit Type as string and x-nullable as true
867880
* If the array has more than two values or two non-null values, do not emit type
868881
* */
869882

870-
var nullableProp = version.Equals(OpenApiSpecVersion.OpenApi2_0)
883+
var nullableProp = version.Equals(OpenApiSpecVersion.OpenApi2_0)
871884
? OpenApiConstants.NullableExtension
872885
: OpenApiConstants.Nullable;
873886

874-
if (array.Length is 1)
887+
if (flagsCount is 1)
875888
{
876-
var value = array[0];
877-
if (value is OpenApiConstants.Null)
889+
if (schemaType is JsonSchemaType.Null)
878890
{
879891
writer.WriteProperty(nullableProp, true);
880892
}
881893
else
882894
{
883-
writer.WriteProperty(OpenApiConstants.Type, value);
895+
writer.WriteProperty(OpenApiConstants.Type, OpenApiTypeMapper.ToIdentifier(schemaType));
884896
}
885897
}
886-
else if (array.Length is 2 && array.Contains(OpenApiConstants.Null))
898+
else if (flagsCount is 2 && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null)
887899
{
888-
// Find the non-null value and write it out
889-
var nonNullValue = array.First(v => v != OpenApiConstants.Null);
890-
writer.WriteProperty(OpenApiConstants.Type, nonNullValue);
900+
foreach (JsonSchemaType flag in System.Enum.GetValues(typeof(JsonSchemaType)))
901+
{
902+
// Skip if the flag is not set or if it's the Null flag
903+
if ((schemaType & flag) == flag && flag != JsonSchemaType.Null && flag != JsonSchemaType.Any)
904+
{
905+
// Write the non-null flag value to the writer
906+
writer.WriteProperty(OpenApiConstants.Type, OpenApiTypeMapper.ToIdentifier(flag));
907+
}
908+
}
891909
if (!Nullable)
892910
{
893911
writer.WriteProperty(nullableProp, true);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ internal OpenApiSchemaReference(OpenApiSchema target, string referenceId)
9292
/// <inheritdoc/>
9393
public override bool UnEvaluatedProperties { get => Target.UnEvaluatedProperties; set => Target.UnEvaluatedProperties = value; }
9494
/// <inheritdoc/>
95-
public override object Type { get => Target.Type; set => Target.Type = value; }
95+
public override JsonSchemaType? Type { get => Target.Type; set => Target.Type = value; }
9696
/// <inheritdoc/>
9797
public override string Format { get => Target.Format; set => Target.Format = value; }
9898
/// <inheritdoc/>

src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ private static OpenApiRequestBody CreateFormBody(ParsingContext context, List<Op
173173
_ => mediaType)
174174
};
175175

176-
foreach (var value in formBody.Content.Values.Where(static x => x.Schema is not null && x.Schema.Properties.Any() && string.IsNullOrEmpty((string)x.Schema.Type)))
177-
value.Schema.Type = "object";
176+
foreach (var value in formBody.Content.Values.Where(static x => x.Schema is not null && x.Schema.Properties.Any() && x.Schema.Type == null))
177+
value.Schema.Type = JsonSchemaType.Object;
178178

179179
return formBody;
180180
}

0 commit comments

Comments
 (0)