Skip to content

Commit bf4a9ea

Browse files
Merge pull request #1897 from microsoft/mk/define-json-schema-type-as-enum
Define JSON schema's Type property as a flaggable enum to allow storing multiple values
2 parents 34b81ce + 5eb0010 commit bf4a9ea

File tree

55 files changed

+940
-551
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+940
-551
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

src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using Microsoft.OpenApi.Exceptions;
67
using Microsoft.OpenApi.Models;
78

89
namespace Microsoft.OpenApi.Extensions
@@ -12,40 +13,81 @@ namespace Microsoft.OpenApi.Extensions
1213
/// </summary>
1314
public static class OpenApiTypeMapper
1415
{
16+
/// <summary>
17+
/// Maps a JsonSchema data type to an identifier.
18+
/// </summary>
19+
/// <param name="schemaType"></param>
20+
/// <returns></returns>
21+
public static string ToIdentifier(this JsonSchemaType? schemaType)
22+
{
23+
return schemaType switch
24+
{
25+
JsonSchemaType.Null => "null",
26+
JsonSchemaType.Boolean => "boolean",
27+
JsonSchemaType.Integer => "integer",
28+
JsonSchemaType.Number => "number",
29+
JsonSchemaType.String => "string",
30+
JsonSchemaType.Array => "array",
31+
JsonSchemaType.Object => "object",
32+
_ => null,
33+
};
34+
}
35+
36+
/// <summary>
37+
/// Converts a schema type's identifier into the enum equivalent
38+
/// </summary>
39+
/// <param name="identifier"></param>
40+
/// <returns></returns>
41+
public static JsonSchemaType ToJsonSchemaType(this string identifier)
42+
{
43+
return identifier switch
44+
{
45+
"null" => JsonSchemaType.Null,
46+
"boolean" => JsonSchemaType.Boolean,
47+
"integer" or "int" => JsonSchemaType.Integer,
48+
"number" or "double" or "float" or "decimal"=> JsonSchemaType.Number,
49+
"string" => JsonSchemaType.String,
50+
"array" => JsonSchemaType.Array,
51+
"object" => JsonSchemaType.Object,
52+
"file" => JsonSchemaType.String, // File is treated as string
53+
_ => throw new OpenApiException(string.Format("Invalid schema type identifier: {0}", identifier))
54+
};
55+
}
56+
1557
private static readonly Dictionary<Type, Func<OpenApiSchema>> _simpleTypeToOpenApiSchema = new()
1658
{
17-
[typeof(bool)] = () => new() { Type = "boolean" },
18-
[typeof(byte)] = () => new() { Type = "string", Format = "byte" },
19-
[typeof(int)] = () => new() { Type = "number", Format = "int32" },
20-
[typeof(uint)] = () => new() { Type = "number", Format = "int32" },
21-
[typeof(long)] = () => new() { Type = "number", Format = "int64" },
22-
[typeof(ulong)] = () => new() { Type = "number", Format = "int64" },
23-
[typeof(float)] = () => new() { Type = "number", Format = "float" },
24-
[typeof(double)] = () => new() { Type = "number", Format = "double" },
25-
[typeof(decimal)] = () => new() { Type = "number", Format = "double" },
26-
[typeof(DateTime)] = () => new() { Type = "string", Format = "date-time" },
27-
[typeof(DateTimeOffset)] = () => new() { Type = "string", Format = "date-time" },
28-
[typeof(Guid)] = () => new() { Type = "string", Format = "uuid" },
29-
[typeof(char)] = () => new() { Type = "string" },
59+
[typeof(bool)] = () => new() { Type = JsonSchemaType.Boolean },
60+
[typeof(byte)] = () => new() { Type = JsonSchemaType.String, Format = "byte" },
61+
[typeof(int)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32" },
62+
[typeof(uint)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32" },
63+
[typeof(long)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64" },
64+
[typeof(ulong)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64" },
65+
[typeof(float)] = () => new() { Type = JsonSchemaType.Number, Format = "float" },
66+
[typeof(double)] = () => new() { Type = JsonSchemaType.Number, Format = "double" },
67+
[typeof(decimal)] = () => new() { Type = JsonSchemaType.Number, Format = "double" },
68+
[typeof(DateTime)] = () => new() { Type = JsonSchemaType.String, Format = "date-time" },
69+
[typeof(DateTimeOffset)] = () => new() { Type = JsonSchemaType.String, Format = "date-time" },
70+
[typeof(Guid)] = () => new() { Type = JsonSchemaType.String, Format = "uuid" },
71+
[typeof(char)] = () => new() { Type = JsonSchemaType.String },
3072

3173
// Nullable types
32-
[typeof(bool?)] = () => new() { Type = "boolean", Nullable = true },
33-
[typeof(byte?)] = () => new() { Type = "string", Format = "byte", Nullable = true },
34-
[typeof(int?)] = () => new() { Type = "number", Format = "int32", Nullable = true },
35-
[typeof(uint?)] = () => new() { Type = "number", Format = "int32", Nullable = true },
36-
[typeof(long?)] = () => new() { Type = "number", Format = "int64", Nullable = true },
37-
[typeof(ulong?)] = () => new() { Type = "number", Format = "int64", Nullable = true },
38-
[typeof(float?)] = () => new() { Type = "number", Format = "float", Nullable = true },
39-
[typeof(double?)] = () => new() { Type = "number", Format = "double", Nullable = true },
40-
[typeof(decimal?)] = () => new() { Type = "number", Format = "double", Nullable = true },
41-
[typeof(DateTime?)] = () => new() { Type = "string", Format = "date-time", Nullable = true },
42-
[typeof(DateTimeOffset?)] = () => new() { Type = "string", Format = "date-time", Nullable = true },
43-
[typeof(Guid?)] = () => new() { Type = "string", Format = "uuid", Nullable = true },
44-
[typeof(char?)] = () => new() { Type = "string", Nullable = true },
74+
[typeof(bool?)] = () => new() { Type = JsonSchemaType.Boolean, Nullable = true },
75+
[typeof(byte?)] = () => new() { Type = JsonSchemaType.String, Format = "byte", Nullable = true },
76+
[typeof(int?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true },
77+
[typeof(uint?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true },
78+
[typeof(long?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true },
79+
[typeof(ulong?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true },
80+
[typeof(float?)] = () => new() { Type = JsonSchemaType.Number, Format = "float", Nullable = true },
81+
[typeof(double?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true },
82+
[typeof(decimal?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true },
83+
[typeof(DateTime?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true },
84+
[typeof(DateTimeOffset?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true },
85+
[typeof(Guid?)] = () => new() { Type = JsonSchemaType.String, Format = "uuid", Nullable = true },
86+
[typeof(char?)] = () => new() { Type = JsonSchemaType.String, Nullable = true },
4587

46-
[typeof(Uri)] = () => new() { Type = "string", Format = "uri" }, // Uri is treated as simple string
47-
[typeof(string)] = () => new() { Type = "string" },
48-
[typeof(object)] = () => new() { Type = "object" }
88+
[typeof(Uri)] = () => new() { Type = JsonSchemaType.String, Format = "uri" }, // Uri is treated as simple string
89+
[typeof(string)] = () => new() { Type = JsonSchemaType.String },
90+
[typeof(object)] = () => new() { Type = JsonSchemaType.Object }
4991
};
5092

5193
/// <summary>
@@ -79,7 +121,7 @@ public static OpenApiSchema MapTypeToOpenApiPrimitiveType(this Type type)
79121

80122
return _simpleTypeToOpenApiSchema.TryGetValue(type, out var result)
81123
? result()
82-
: new() { Type = "string" };
124+
: new() { Type = JsonSchemaType.String };
83125
}
84126

85127
/// <summary>
@@ -95,7 +137,7 @@ public static Type MapOpenApiPrimitiveTypeToSimpleType(this OpenApiSchema schema
95137
throw new ArgumentNullException(nameof(schema));
96138
}
97139

98-
var type = (schema.Type?.ToString().ToLowerInvariant(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch
140+
var type = (schema.Type.ToIdentifier(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch
99141
{
100142
("boolean", null, false) => typeof(bool),
101143
// integer is technically not valid with format, but we must provide some compatibility
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 a null type.
16+
/// </summary>
17+
Null = 1,
18+
19+
/// <summary>
20+
/// Represents a boolean type.
21+
/// </summary>
22+
Boolean = 2,
23+
24+
/// <summary>
25+
/// Represents an integer type.
26+
/// </summary>
27+
Integer = 4,
28+
29+
/// <summary>
30+
/// Represents a number type.
31+
/// </summary>
32+
Number = 8,
33+
34+
/// <summary>
35+
/// Represents a string type.
36+
/// </summary>
37+
String = 16,
38+
39+
/// <summary>
40+
/// Represents an object type.
41+
/// </summary>
42+
Object = 32,
43+
44+
/// <summary>
45+
/// Represents an array type.
46+
/// </summary>
47+
Array = 64,
48+
}
49+
}

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/OpenApiRequestBody.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using Microsoft.OpenApi.Any;
8+
using Microsoft.OpenApi.Extensions;
89
using Microsoft.OpenApi.Interfaces;
910
using Microsoft.OpenApi.Writers;
1011

@@ -141,11 +142,11 @@ internal IEnumerable<OpenApiFormDataParameter> ConvertToFormDataParameters()
141142
foreach (var property in Content.First().Value.Schema.Properties)
142143
{
143144
var paramSchema = property.Value;
144-
if ("string".Equals(paramSchema.Type.ToString(), StringComparison.OrdinalIgnoreCase)
145+
if ("string".Equals(paramSchema.Type.ToIdentifier(), StringComparison.OrdinalIgnoreCase)
145146
&& ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)
146147
|| "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)))
147148
{
148-
paramSchema.Type = "file";
149+
paramSchema.Type = "file".ToJsonSchemaType();
149150
paramSchema.Format = null;
150151
}
151152
yield return new()

0 commit comments

Comments
 (0)