Skip to content

Adds nullable to double schema conversions (#589) #628

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 7 commits into from
Jan 2, 2025
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
7 changes: 2 additions & 5 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 All @@ -13,37 +15,37 @@
/// <summary>
/// application/json
/// </summary>
public static string ApplicationJsonMediaType = "application/json";

Check warning on line 18 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'ApplicationJsonMediaType' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// application/xml
/// </summary>
public static string ApplicationXmlMediaType = "application/xml";

Check warning on line 23 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'ApplicationXmlMediaType' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// application/octet-stream
/// </summary>
public static string ApplicationOctetStreamMediaType = "application/octet-stream";

Check warning on line 28 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'ApplicationOctetStreamMediaType' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// Status code class: 2XX
/// </summary>
public static string StatusCodeClass2XX = "2XX";

Check warning on line 33 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'StatusCodeClass2XX' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// Status code: 200
/// </summary>
public static string StatusCode200 = "200";

Check warning on line 38 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'StatusCode200' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// Status code: 201
/// </summary>
public static string StatusCode201 = "201";

Check warning on line 43 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'StatusCode201' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// Status code: 204
/// </summary>
public static string StatusCode204 = "204";

Check warning on line 48 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'StatusCode204' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// Status code: default
Expand All @@ -58,7 +60,7 @@
/// <summary>
/// Status code class: 5XX
/// </summary>
public static string StatusCodeClass5XX = "5XX";

Check warning on line 63 in src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

View workflow job for this annotation

GitHub Actions / Build

Change the visibility of 'StatusCodeClass5XX' or make it 'const' or 'readonly'. (https://rules.sonarsource.com/csharp/RSPEC-2223)

/// <summary>
/// Edm model error extension key.
Expand Down Expand Up @@ -165,11 +167,6 @@
/// </summary>
public static string StringType = "string";

/// <summary>
/// integer type
/// </summary>
public static string IntegerType = "integer";

/// <summary>
/// number type
/// </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 All @@ -195,8 +196,8 @@ public static OpenApiSchema CreateSchema(this ODataContext context, IEdmPrimitiv
case EdmPrimitiveTypeKind.Double: // double
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = Constants.NumberType, Format = "double" },
new OpenApiSchema { Type = Constants.StringType },
new OpenApiSchema { Type = Constants.NumberType, Format = "double", Nullable = true },
new OpenApiSchema { Type = Constants.StringType, Nullable = true },
new OpenApiSchema
{
UnresolvedReference = true,
Expand All @@ -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 @@ -121,12 +121,14 @@

schemas[Constants.ReferenceNumericName] = new()
{
Enum = new List<IOpenApiAny>
{
Type = Constants.StringType,
Nullable = true,
Enum =
[
new OpenApiString("-INF"),
new OpenApiString("INF"),
new OpenApiString("NaN")
}
]
};

if (context.Settings.EnableODataAnnotationReferencesForResponses)
Expand Down Expand Up @@ -631,7 +633,7 @@
// properties
foreach (var property in structuredType.Properties())
{
// IOpenApiAny item;

Check warning on line 636 in src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)
IEdmTypeReference propertyType = property.Type;

IOpenApiAny item = GetTypeNameForExample(context, propertyType);
Expand Down Expand Up @@ -757,7 +759,7 @@
// The type 'System.Double' is not supported in Open API document.
case EdmPrimitiveTypeKind.Double:
/*
{

Check warning on line 762 in src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)
double result;
if (Double.TryParse(property.DefaultValueString, out result))
{
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