Skip to content

Commit b919ea8

Browse files
authored
Merge pull request #593 from microsoft/fix/nullable-numbers
fix/nullable numbers
2 parents 85893d7 + 032a923 commit b919ea8

18 files changed

+1683
-1666
lines changed

src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// ------------------------------------------------------------
55

6+
using System;
7+
68
namespace Microsoft.OpenApi.OData.Common
79
{
810
/// <summary>
@@ -168,6 +170,7 @@ internal static class Constants
168170
/// <summary>
169171
/// integer type
170172
/// </summary>
173+
[Obsolete("integer is not a valid OpenAPI type. Use number instead.")]
171174
public static string IntegerType = "integer";
172175

173176
/// <summary>

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiEdmTypeSchemaGenerator.cs

Lines changed: 61 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ public static OpenApiSchema CreateEdmTypeSchema(this ODataContext context, IEdmT
3333
Utils.CheckArgumentNull(edmTypeReference, nameof(edmTypeReference));
3434

3535
switch (edmTypeReference.TypeKind())
36-
{
37-
// Collection-valued structural and navigation are represented as Schema Objects of type array.
38-
// The value of the items keyword is a Schema Object specifying the type of the items.
36+
{
37+
// Collection-valued structural and navigation are represented as Schema Objects of type array.
38+
// The value of the items keyword is a Schema Object specifying the type of the items.
3939
case EdmTypeKind.Collection:
4040

4141
IEdmTypeReference typeRef = edmTypeReference.AsCollection().ElementType();
4242
OpenApiSchema schema;
43-
schema = typeRef.TypeKind() == EdmTypeKind.Complex || typeRef.TypeKind() == EdmTypeKind.Entity
44-
? context.CreateStructuredTypeSchema(typeRef.AsStructured(), true)
45-
: context.CreateEdmTypeSchema(typeRef);
46-
43+
schema = typeRef.TypeKind() == EdmTypeKind.Complex || typeRef.TypeKind() == EdmTypeKind.Entity
44+
? context.CreateStructuredTypeSchema(typeRef.AsStructured(), true)
45+
: context.CreateEdmTypeSchema(typeRef);
46+
4747
return new OpenApiSchema
4848
{
4949
Type = "array",
@@ -133,7 +133,8 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
133133
}
134134

135135
// Nullable properties are marked with the keyword nullable and a value of true.
136-
schema.Nullable = primitiveType.IsNullable ? true : false;
136+
// nullable cannot be true when type is empty, often common in anyof/allOf since individual entries are nullable
137+
schema.Nullable = !string.IsNullOrEmpty(schema.Type) && primitiveType.IsNullable;
137138
}
138139

139140
return schema;
@@ -169,7 +170,7 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
169170
schema.Default = new OpenApiBoolean(false);
170171
break;
171172
case EdmPrimitiveTypeKind.Byte: // byte
172-
schema.Type = Constants.IntegerType;
173+
schema.Type = Constants.NumberType;
173174
schema.Format = "uint8";
174175
break;
175176
case EdmPrimitiveTypeKind.DateTimeOffset: // datetime offset
@@ -182,8 +183,8 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
182183
{
183184
schema.OneOf = new List<OpenApiSchema>
184185
{
185-
new OpenApiSchema { Type = Constants.NumberType, Format = Constants.DecimalFormat },
186-
new OpenApiSchema { Type = Constants.StringType },
186+
new OpenApiSchema { Type = Constants.NumberType, Format = Constants.DecimalFormat, Nullable = true },
187+
new OpenApiSchema { Type = Constants.StringType, Nullable = true },
187188
};
188189
}
189190
else
@@ -211,8 +212,8 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
211212
case EdmPrimitiveTypeKind.Single: // single
212213
schema.OneOf = new List<OpenApiSchema>
213214
{
214-
new OpenApiSchema { Type = Constants.NumberType, Format = "float" },
215-
new OpenApiSchema { Type = Constants.StringType },
215+
new OpenApiSchema { Type = Constants.NumberType, Format = "float", Nullable = true },
216+
new OpenApiSchema { Type = Constants.StringType, Nullable = true },
216217
new OpenApiSchema
217218
{
218219
UnresolvedReference = true,
@@ -230,13 +231,13 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
230231
schema.Pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
231232
break;
232233
case EdmPrimitiveTypeKind.Int16:
233-
schema.Type = Constants.IntegerType;
234+
schema.Type = Constants.NumberType;
234235
schema.Format = "int16";
235236
schema.Minimum = Int16.MinValue; // -32768
236237
schema.Maximum = Int16.MaxValue; // 32767
237238
break;
238239
case EdmPrimitiveTypeKind.Int32:
239-
schema.Type = Constants.IntegerType;
240+
schema.Type = Constants.NumberType;
240241
schema.Format = "int32";
241242
schema.Minimum = Int32.MinValue; // -2147483648
242243
schema.Maximum = Int32.MaxValue; // 2147483647
@@ -246,18 +247,18 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
246247
{
247248
schema.OneOf = new List<OpenApiSchema>
248249
{
249-
new OpenApiSchema { Type = Constants.IntegerType, Format = Constants.Int64Format },
250-
new OpenApiSchema { Type = Constants.StringType }
250+
new OpenApiSchema { Type = Constants.NumberType, Format = Constants.Int64Format, Nullable = true },
251+
new OpenApiSchema { Type = Constants.StringType, Nullable = true }
251252
};
252253
}
253254
else
254255
{
255-
schema.Type = Constants.IntegerType;
256+
schema.Type = Constants.NumberType;
256257
schema.Format = Constants.Int64Format;
257258
}
258259
break;
259260
case EdmPrimitiveTypeKind.SByte:
260-
schema.Type = Constants.IntegerType;
261+
schema.Type = Constants.NumberType;
261262
schema.Format = "int8";
262263
schema.Minimum = SByte.MinValue; // -128
263264
schema.Maximum = SByte.MaxValue; // 127
@@ -469,47 +470,47 @@ private static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEd
469470
private static OpenApiSchema CreateStructuredTypeSchema(this ODataContext context, IEdmStructuredTypeReference typeReference, bool isTypeCollection = false)
470471
{
471472
Debug.Assert(context != null);
472-
Debug.Assert(typeReference != null);
473-
474-
OpenApiSchema schema = new OpenApiSchema();
475-
476-
// AnyOf will only be valid openApi for version 3
477-
// otherwise the reference should be set directly
478-
// as per OASIS documentation for openApi version 2
479-
// Collections of structured types cannot be nullable
480-
if (typeReference.IsNullable && !isTypeCollection &&
481-
(context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0))
482-
{
483-
schema.Reference = null;
484-
schema.AnyOf = new List<OpenApiSchema>
485-
{
486-
new OpenApiSchema
487-
{
488-
UnresolvedReference = true,
489-
Reference = new OpenApiReference
490-
{
491-
Type = ReferenceType.Schema,
492-
Id = typeReference.Definition.FullTypeName()
493-
}
494-
},
495-
new OpenApiSchema
496-
{
497-
Type = "object",
498-
Nullable = true
499-
}
500-
};
501-
}
502-
else
503-
{
504-
schema.Type = null;
505-
schema.AnyOf = null;
506-
schema.Reference = new OpenApiReference
507-
{
508-
Type = ReferenceType.Schema,
509-
Id = typeReference.Definition.FullTypeName()
510-
};
511-
schema.UnresolvedReference = true;
512-
schema.Nullable = typeReference.IsNullable;
473+
Debug.Assert(typeReference != null);
474+
475+
OpenApiSchema schema = new OpenApiSchema();
476+
477+
// AnyOf will only be valid openApi for version 3
478+
// otherwise the reference should be set directly
479+
// as per OASIS documentation for openApi version 2
480+
// Collections of structured types cannot be nullable
481+
if (typeReference.IsNullable && !isTypeCollection &&
482+
(context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0))
483+
{
484+
schema.Reference = null;
485+
schema.AnyOf = new List<OpenApiSchema>
486+
{
487+
new OpenApiSchema
488+
{
489+
UnresolvedReference = true,
490+
Reference = new OpenApiReference
491+
{
492+
Type = ReferenceType.Schema,
493+
Id = typeReference.Definition.FullTypeName()
494+
}
495+
},
496+
new OpenApiSchema
497+
{
498+
Type = "object",
499+
Nullable = true
500+
}
501+
};
502+
}
503+
else
504+
{
505+
schema.Type = null;
506+
schema.AnyOf = null;
507+
schema.Reference = new OpenApiReference
508+
{
509+
Type = ReferenceType.Schema,
510+
Id = typeReference.Definition.FullTypeName()
511+
};
512+
schema.UnresolvedReference = true;
513+
schema.Nullable = typeReference.IsNullable;
513514
}
514515

515516
return schema;

src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<TargetFrameworks>net8.0</TargetFrameworks>
1616
<PackageId>Microsoft.OpenApi.OData</PackageId>
1717
<SignAssembly>true</SignAssembly>
18-
<Version>2.0.0-preview.3</Version>
18+
<Version>2.0.0-preview.4</Version>
1919
<Description>This package contains the codes you need to convert OData CSDL to Open API Document of Model.</Description>
2020
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
2121
<PackageTags>Microsoft OpenApi OData EDM</PackageTags>
@@ -28,6 +28,8 @@
2828
- Adds nullable to double schema conversions #581
2929
- Updates tag names for actions/functions operations #585
3030
- Creates unique operation ids for paths with composable overloaded functions #580
31+
- Further fixes for double/decimal/float schema conversions #581
32+
- Replaced integer types by number types
3133
</PackageReleaseNotes>
3234
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
3335
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>

test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiEdmTypeSchemaGeneratorTest.cs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public void CreateEdmTypeSchemaReturnSchemaForNullableCollectionPrimitiveType()
151151
""items"": {
152152
""maximum"": 2147483647,
153153
""minimum"": -2147483648,
154-
""type"": ""integer"",
154+
""type"": ""number"",
155155
""format"": ""int32"",
156156
""nullable"": true
157157
}
@@ -358,7 +358,7 @@ public void CreateEdmTypeSchemaReturnSchemaForInt32(bool isNullable)
358358
Assert.Equal(@"{
359359
""maximum"": 2147483647,
360360
""minimum"": -2147483648,
361-
""type"": ""integer"",
361+
""type"": ""number"",
362362
""format"": ""int32"",
363363
""nullable"": true
364364
}".ChangeLineBreaks(), json);
@@ -368,7 +368,7 @@ public void CreateEdmTypeSchemaReturnSchemaForInt32(bool isNullable)
368368
Assert.Equal(@"{
369369
""maximum"": 2147483647,
370370
""minimum"": -2147483648,
371-
""type"": ""integer"",
371+
""type"": ""number"",
372372
""format"": ""int32""
373373
}".ChangeLineBreaks(), json);
374374
}
@@ -401,15 +401,20 @@ public void CreateEdmTypeSchemaReturnSchemaForDecimal(bool isNullable, bool IEEE
401401
Assert.Null(schema.Type);
402402
Assert.NotNull(schema.OneOf);
403403
Assert.Equal(2, schema.OneOf.Count);
404-
Assert.Equal(new[] { "number", "string" }, schema.OneOf.Select(a => a.Type));
404+
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
405+
Assert.NotNull(numberSchema);
406+
Assert.True(numberSchema.Nullable);
407+
var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
408+
Assert.NotNull(stringSchema);
409+
Assert.True(stringSchema.Nullable);
410+
Assert.False(schema.Nullable);
405411
}
406412
else
407413
{
408414
Assert.Equal("number", schema.Type);
409415
Assert.Null(schema.OneOf);
416+
Assert.Equal(isNullable, schema.Nullable);
410417
}
411-
412-
Assert.Equal(isNullable, schema.Nullable);
413418
}
414419

415420
[Theory]
@@ -439,15 +444,20 @@ public void CreateEdmTypeSchemaReturnSchemaForInt64(bool isNullable, bool IEEE75
439444
Assert.Null(schema.Type);
440445
Assert.NotNull(schema.OneOf);
441446
Assert.Equal(2, schema.OneOf.Count);
442-
Assert.Equal(new[] { "integer", "string" }, schema.OneOf.Select(a => a.Type));
447+
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
448+
Assert.NotNull(numberSchema);
449+
Assert.True(numberSchema.Nullable);
450+
var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
451+
Assert.NotNull(stringSchema);
452+
Assert.True(stringSchema.Nullable);
453+
Assert.False(schema.Nullable);
443454
}
444455
else
445456
{
446-
Assert.Equal("integer", schema.Type);
457+
Assert.Equal("number", schema.Type);
447458
Assert.Null(schema.AnyOf);
459+
Assert.Equal(isNullable, schema.Nullable);
448460
}
449-
450-
Assert.Equal(isNullable, schema.Nullable);
451461
}
452462

453463
[Theory]
@@ -502,8 +512,16 @@ public void CreateEdmTypeSchemaReturnSchemaForDouble(bool isNullable)
502512
// & Assert
503513
Assert.Null(schema.Type);
504514

505-
Assert.Equal("double", schema.OneOf.FirstOrDefault(x => !string.IsNullOrEmpty(x.Format))?.Format);
506-
Assert.Equal(isNullable, schema.Nullable);
515+
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
516+
Assert.NotNull(numberSchema);
517+
Assert.True(numberSchema.Nullable);
518+
Assert.Equal("double", numberSchema.Format, StringComparer.OrdinalIgnoreCase);
519+
520+
var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
521+
Assert.NotNull(stringSchema);
522+
Assert.True(stringSchema.Nullable);
523+
524+
Assert.False(schema.Nullable);
507525

508526
Assert.Null(schema.AnyOf);
509527

@@ -528,8 +546,16 @@ public void CreateEdmTypeSchemaReturnSchemaForSingle(bool isNullable)
528546
// & Assert
529547
Assert.Null(schema.Type);
530548

531-
Assert.Equal("float", schema.OneOf.FirstOrDefault(x => !string.IsNullOrEmpty(x.Format))?.Format);
532-
Assert.Equal(isNullable, schema.Nullable);
549+
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
550+
Assert.NotNull(numberSchema);
551+
Assert.True(numberSchema.Nullable);
552+
Assert.Equal("float", numberSchema.Format, StringComparer.OrdinalIgnoreCase);
553+
554+
var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
555+
Assert.NotNull(stringSchema);
556+
Assert.True(stringSchema.Nullable);
557+
558+
Assert.False(schema.Nullable);
533559

534560
Assert.Null(schema.AnyOf);
535561

0 commit comments

Comments
 (0)