Skip to content

fix/nullable numbers #593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 10, 2024
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
3 changes: 3 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------

using System;

namespace Microsoft.OpenApi.OData.Common
{
/// <summary>
Expand Down Expand Up @@ -168,6 +170,7 @@ internal static class Constants
/// <summary>
/// integer type
/// </summary>
[Obsolete("integer is not a valid OpenAPI type. Use number instead.")]
public static string IntegerType = "integer";

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ public static OpenApiSchema CreateEdmTypeSchema(this ODataContext context, IEdmT
Utils.CheckArgumentNull(edmTypeReference, nameof(edmTypeReference));

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

IEdmTypeReference typeRef = edmTypeReference.AsCollection().ElementType();
OpenApiSchema schema;
schema = typeRef.TypeKind() == EdmTypeKind.Complex || typeRef.TypeKind() == EdmTypeKind.Entity
? context.CreateStructuredTypeSchema(typeRef.AsStructured(), true)
: context.CreateEdmTypeSchema(typeRef);
schema = typeRef.TypeKind() == EdmTypeKind.Complex || typeRef.TypeKind() == EdmTypeKind.Entity
? context.CreateStructuredTypeSchema(typeRef.AsStructured(), true)
: context.CreateEdmTypeSchema(typeRef);

return new OpenApiSchema
{
Type = "array",
Expand Down Expand Up @@ -133,7 +133,8 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
}

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

return schema;
Expand Down Expand Up @@ -169,7 +170,7 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
schema.Default = new OpenApiBoolean(false);
break;
case EdmPrimitiveTypeKind.Byte: // byte
schema.Type = Constants.IntegerType;
schema.Type = Constants.NumberType;
schema.Format = "uint8";
break;
case EdmPrimitiveTypeKind.DateTimeOffset: // datetime offset
Expand All @@ -182,8 +183,8 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
{
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = Constants.NumberType, Format = Constants.DecimalFormat },
new OpenApiSchema { Type = Constants.StringType },
new OpenApiSchema { Type = Constants.NumberType, Format = Constants.DecimalFormat, Nullable = true },
new OpenApiSchema { Type = Constants.StringType, Nullable = true },
};
}
else
Expand Down Expand Up @@ -211,8 +212,8 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
case EdmPrimitiveTypeKind.Single: // single
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = Constants.NumberType, Format = "float" },
new OpenApiSchema { Type = Constants.StringType },
new OpenApiSchema { Type = Constants.NumberType, Format = "float", Nullable = true },
new OpenApiSchema { Type = Constants.StringType, Nullable = true },
new OpenApiSchema
{
UnresolvedReference = true,
Expand All @@ -230,13 +231,13 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
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}$";
break;
case EdmPrimitiveTypeKind.Int16:
schema.Type = Constants.IntegerType;
schema.Type = Constants.NumberType;
schema.Format = "int16";
schema.Minimum = Int16.MinValue; // -32768
schema.Maximum = Int16.MaxValue; // 32767
break;
case EdmPrimitiveTypeKind.Int32:
schema.Type = Constants.IntegerType;
schema.Type = Constants.NumberType;
schema.Format = "int32";
schema.Minimum = Int32.MinValue; // -2147483648
schema.Maximum = Int32.MaxValue; // 2147483647
Expand All @@ -246,18 +247,18 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
{
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = Constants.IntegerType, Format = Constants.Int64Format },
new OpenApiSchema { Type = Constants.StringType }
new OpenApiSchema { Type = Constants.NumberType, Format = Constants.Int64Format, Nullable = true },
new OpenApiSchema { Type = Constants.StringType, Nullable = true }
};
}
else
{
schema.Type = Constants.IntegerType;
schema.Type = Constants.NumberType;
schema.Format = Constants.Int64Format;
}
break;
case EdmPrimitiveTypeKind.SByte:
schema.Type = Constants.IntegerType;
schema.Type = Constants.NumberType;
schema.Format = "int8";
schema.Minimum = SByte.MinValue; // -128
schema.Maximum = SByte.MaxValue; // 127
Expand Down Expand Up @@ -469,47 +470,47 @@ private static OpenApiSchema CreateEnumTypeSchema(this ODataContext context, IEd
private static OpenApiSchema CreateStructuredTypeSchema(this ODataContext context, IEdmStructuredTypeReference typeReference, bool isTypeCollection = false)
{
Debug.Assert(context != null);
Debug.Assert(typeReference != null);
OpenApiSchema schema = new OpenApiSchema();
// AnyOf will only be valid openApi for version 3
// otherwise the reference should be set directly
// as per OASIS documentation for openApi version 2
// Collections of structured types cannot be nullable
if (typeReference.IsNullable && !isTypeCollection &&
(context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0))
{
schema.Reference = null;
schema.AnyOf = new List<OpenApiSchema>
{
new OpenApiSchema
{
UnresolvedReference = true,
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = typeReference.Definition.FullTypeName()
}
},
new OpenApiSchema
{
Type = "object",
Nullable = true
}
};
}
else
{
schema.Type = null;
schema.AnyOf = null;
schema.Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = typeReference.Definition.FullTypeName()
};
schema.UnresolvedReference = true;
schema.Nullable = typeReference.IsNullable;
Debug.Assert(typeReference != null);

OpenApiSchema schema = new OpenApiSchema();

// AnyOf will only be valid openApi for version 3
// otherwise the reference should be set directly
// as per OASIS documentation for openApi version 2
// Collections of structured types cannot be nullable
if (typeReference.IsNullable && !isTypeCollection &&
(context.Settings.OpenApiSpecVersion >= OpenApiSpecVersion.OpenApi3_0))
{
schema.Reference = null;
schema.AnyOf = new List<OpenApiSchema>
{
new OpenApiSchema
{
UnresolvedReference = true,
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = typeReference.Definition.FullTypeName()
}
},
new OpenApiSchema
{
Type = "object",
Nullable = true
}
};
}
else
{
schema.Type = null;
schema.AnyOf = null;
schema.Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = typeReference.Definition.FullTypeName()
};
schema.UnresolvedReference = true;
schema.Nullable = typeReference.IsNullable;
}

return schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<TargetFrameworks>net8.0</TargetFrameworks>
<PackageId>Microsoft.OpenApi.OData</PackageId>
<SignAssembly>true</SignAssembly>
<Version>2.0.0-preview.3</Version>
<Version>2.0.0-preview.4</Version>
<Description>This package contains the codes you need to convert OData CSDL to Open API Document of Model.</Description>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageTags>Microsoft OpenApi OData EDM</PackageTags>
Expand All @@ -28,6 +28,8 @@
- Adds nullable to double schema conversions #581
- Updates tag names for actions/functions operations #585
- Creates unique operation ids for paths with composable overloaded functions #580
- Further fixes for double/decimal/float schema conversions #581
- Replaced integer types by number types
</PackageReleaseNotes>
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public void CreateEdmTypeSchemaReturnSchemaForNullableCollectionPrimitiveType()
""items"": {
""maximum"": 2147483647,
""minimum"": -2147483648,
""type"": ""integer"",
""type"": ""number"",
""format"": ""int32"",
""nullable"": true
}
Expand Down Expand Up @@ -358,7 +358,7 @@ public void CreateEdmTypeSchemaReturnSchemaForInt32(bool isNullable)
Assert.Equal(@"{
""maximum"": 2147483647,
""minimum"": -2147483648,
""type"": ""integer"",
""type"": ""number"",
""format"": ""int32"",
""nullable"": true
}".ChangeLineBreaks(), json);
Expand All @@ -368,7 +368,7 @@ public void CreateEdmTypeSchemaReturnSchemaForInt32(bool isNullable)
Assert.Equal(@"{
""maximum"": 2147483647,
""minimum"": -2147483648,
""type"": ""integer"",
""type"": ""number"",
""format"": ""int32""
}".ChangeLineBreaks(), json);
}
Expand Down Expand Up @@ -401,15 +401,20 @@ public void CreateEdmTypeSchemaReturnSchemaForDecimal(bool isNullable, bool IEEE
Assert.Null(schema.Type);
Assert.NotNull(schema.OneOf);
Assert.Equal(2, schema.OneOf.Count);
Assert.Equal(new[] { "number", "string" }, schema.OneOf.Select(a => a.Type));
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(numberSchema);
Assert.True(numberSchema.Nullable);
var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(stringSchema);
Assert.True(stringSchema.Nullable);
Assert.False(schema.Nullable);
}
else
{
Assert.Equal("number", schema.Type);
Assert.Null(schema.OneOf);
Assert.Equal(isNullable, schema.Nullable);
}

Assert.Equal(isNullable, schema.Nullable);
}

[Theory]
Expand Down Expand Up @@ -439,15 +444,20 @@ public void CreateEdmTypeSchemaReturnSchemaForInt64(bool isNullable, bool IEEE75
Assert.Null(schema.Type);
Assert.NotNull(schema.OneOf);
Assert.Equal(2, schema.OneOf.Count);
Assert.Equal(new[] { "integer", "string" }, schema.OneOf.Select(a => a.Type));
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(numberSchema);
Assert.True(numberSchema.Nullable);
var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(stringSchema);
Assert.True(stringSchema.Nullable);
Assert.False(schema.Nullable);
}
else
{
Assert.Equal("integer", schema.Type);
Assert.Equal("number", schema.Type);
Assert.Null(schema.AnyOf);
Assert.Equal(isNullable, schema.Nullable);
}

Assert.Equal(isNullable, schema.Nullable);
}

[Theory]
Expand Down Expand Up @@ -502,8 +512,16 @@ public void CreateEdmTypeSchemaReturnSchemaForDouble(bool isNullable)
// & Assert
Assert.Null(schema.Type);

Assert.Equal("double", schema.OneOf.FirstOrDefault(x => !string.IsNullOrEmpty(x.Format))?.Format);
Assert.Equal(isNullable, schema.Nullable);
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(numberSchema);
Assert.True(numberSchema.Nullable);
Assert.Equal("double", numberSchema.Format, StringComparer.OrdinalIgnoreCase);

var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(stringSchema);
Assert.True(stringSchema.Nullable);

Assert.False(schema.Nullable);

Assert.Null(schema.AnyOf);

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

Assert.Equal("float", schema.OneOf.FirstOrDefault(x => !string.IsNullOrEmpty(x.Format))?.Format);
Assert.Equal(isNullable, schema.Nullable);
var numberSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("number", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(numberSchema);
Assert.True(numberSchema.Nullable);
Assert.Equal("float", numberSchema.Format, StringComparer.OrdinalIgnoreCase);

var stringSchema = schema.OneOf.FirstOrDefault(x => x.Type.Equals("string", StringComparison.OrdinalIgnoreCase));
Assert.NotNull(stringSchema);
Assert.True(stringSchema.Nullable);

Assert.False(schema.Nullable);

Assert.Null(schema.AnyOf);

Expand Down
Loading
Loading