OpenAPI.NET.OData/src/Microsoft.OpenApi.OData.Reader/Generator/SchemaExtensions.cs

528 lines
22 KiB
C#

// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generator
{
/// <summary>
/// See https://github.com/oasis-tcs/odata-openapi/blob/master/examples/odata-definitions.json
/// </summary>
internal static class SchemaExtensions
{
private static string _externalResource = "https://raw.githubusercontent.com/oasis-tcs/odata-openapi/master/examples/odata-definitions.json";
public static OpenApiSchema CreatePropertySchema(this IEdmStructuralProperty property)
{
if (property == null)
{
throw Error.ArgumentNull(nameof(property));
}
OpenApiSchema schema = property.Type.CreateSchema();
schema.Default = CreateDefault(property);
return schema;
}
public static OpenApiSchema CreateSchema(this IEdmTypeReference reference)
{
if (reference == null)
{
return null;
}
switch (reference.TypeKind())
{
case EdmTypeKind.Collection:
return new OpenApiSchema
{
Type = "array",
Items = CreateSchema(reference.AsCollection().ElementType())
};
case EdmTypeKind.Complex:
case EdmTypeKind.Entity:
case EdmTypeKind.EntityReference:
case EdmTypeKind.Enum:
return new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = reference.Definition.FullTypeName()
}
};
case EdmTypeKind.Primitive:
IEdmPrimitiveTypeReference primitiveTypeReference = (IEdmPrimitiveTypeReference)reference;
return primitiveTypeReference.CreateSchema();
case EdmTypeKind.TypeDefinition:
return ((IEdmTypeDefinitionReference)reference).TypeDefinition().UnderlyingType.CreateSchema();
case EdmTypeKind.None:
default:
throw Error.NotSupported(String.Format("Not supported {0} type kind.", reference.TypeKind()));
}
}
public static OpenApiSchema CreateSchema(this IEdmPrimitiveTypeReference primitiveType)
{
OpenApiSchema schema = primitiveType.PrimitiveDefinition().CreateSchema();
if (schema != null)
{
switch(primitiveType.PrimitiveKind())
{
case EdmPrimitiveTypeKind.Binary: // binary
IEdmBinaryTypeReference binaryTypeReference = (IEdmBinaryTypeReference)primitiveType;
schema.MaxLength = binaryTypeReference.MaxLength;
break;
case EdmPrimitiveTypeKind.Decimal: // decimal
IEdmDecimalTypeReference decimalTypeReference = (IEdmDecimalTypeReference)primitiveType;
if (decimalTypeReference.Precision != null)
{
if (decimalTypeReference.Scale != null)
{
// The precision is represented with the maximum and minimum keywords and a value of ±(10^ (precision - scale) - 10^ scale).
double tmp = Math.Pow(10, decimalTypeReference.Precision.Value - decimalTypeReference.Scale.Value)
- Math.Pow(10, -decimalTypeReference.Scale.Value);
schema.Minimum = (decimal?)(tmp * -1.0);
schema.Maximum = (decimal?)(tmp);
}
else
{
// If the scale facet has a numeric value, and ±(10^precision - 1) if the scale is variable
double tmp = Math.Pow(10, decimalTypeReference.Precision.Value) - 1;
schema.Minimum = (decimal?)(tmp * -1.0);
schema.Maximum = (decimal?)(tmp);
}
}
// The scale of properties of type Edm.Decimal are represented with the OpenAPI Specification keyword multipleOf and a value of 10 ^ -scale
schema.MultipleOf = decimalTypeReference.Scale == null ? null : (decimal?)(Math.Pow(10, decimalTypeReference.Scale.Value * -1));
break;
case EdmPrimitiveTypeKind.String: // string
IEdmStringTypeReference stringTypeReference = (IEdmStringTypeReference)primitiveType;
schema.MaxLength = stringTypeReference.MaxLength;
break;
}
// Nullable properties are marked with the keyword nullable and a value of true.
schema.Nullable = primitiveType.IsNullable ? true : false;
}
return schema;
}
public static OpenApiSchema CreateSchema(this IEdmPrimitiveType primitiveType)
{
if (primitiveType == null)
{
return null;
}
// Spec has different configure for double, AnyOf or OneOf?
OpenApiSchema schema = new OpenApiSchema
{
AllOf = null,
OneOf = null,
AnyOf = null
};
switch (primitiveType.PrimitiveKind)
{
case EdmPrimitiveTypeKind.Binary: // binary
schema.Type = "string";
schema.Format = "base64url";
break;
case EdmPrimitiveTypeKind.Boolean: // boolean
schema.Type = "boolean";
schema.Default = new OpenApiBoolean(false);
break;
case EdmPrimitiveTypeKind.Byte: // byte
schema.Type = "integer";
schema.Format = "uint8";
break;
case EdmPrimitiveTypeKind.DateTimeOffset: // datetime offset
schema.Type = "string";
schema.Format = "date-time";
schema.Pattern = "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$";
break;
case EdmPrimitiveTypeKind.Decimal: // decimal
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = "number" },
new OpenApiSchema { Type = "string" },
};
schema.Format = "decimal";
break;
case EdmPrimitiveTypeKind.Double: // double
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = "number" },
new OpenApiSchema { Type = "string" },
new OpenApiSchema
{
Enum = new List<IOpenApiAny>
{
new OpenApiString("-INF"),
new OpenApiString("INF"),
new OpenApiString("NaN")
}
}
};
schema.Format = "double";
break;
case EdmPrimitiveTypeKind.Single: // single
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = "number" },
new OpenApiSchema { Type = "string" },
new OpenApiSchema
{
Enum = new List<IOpenApiAny>
{
new OpenApiString("-INF"),
new OpenApiString("INF"),
new OpenApiString("NaN")
}
}
};
schema.Format = "float";
break;
case EdmPrimitiveTypeKind.Guid: // guid
schema.Type = "string";
schema.Format = "uuid";
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 = "integer";
schema.Format = "int16";
schema.Minimum = Int16.MinValue; // -32768
schema.Maximum = Int16.MaxValue; // 32767
break;
case EdmPrimitiveTypeKind.Int32:
schema.Type = "integer";
schema.Format = "int32";
schema.Minimum = Int32.MinValue; // -2147483648
schema.Maximum = Int32.MaxValue; // 2147483647
break;
case EdmPrimitiveTypeKind.Int64:
schema.OneOf = new List<OpenApiSchema>
{
new OpenApiSchema { Type = "integer" },
new OpenApiSchema { Type = "string" }
};
schema.Format = "int64";
break;
case EdmPrimitiveTypeKind.SByte:
schema.Type = "integer";
schema.Format = "int8";
schema.Minimum = SByte.MinValue; // -128
schema.Maximum = SByte.MaxValue; // 127
break;
case EdmPrimitiveTypeKind.String: // string
schema.Type = "string";
break;
case EdmPrimitiveTypeKind.Stream: // stream
schema.Type = "string";
schema.Format = "base64url";
break;
case EdmPrimitiveTypeKind.Duration: // duration
schema.Type = "string";
schema.Format = "duration";
schema.Pattern = "^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+([.][0-9]+)?S)?)?$";
break;
case EdmPrimitiveTypeKind.Date:
schema.Type = "string";
schema.Format = "date";
schema.Pattern = "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$";
break;
case EdmPrimitiveTypeKind.TimeOfDay:
schema.Type = "string";
schema.Format = "time";
schema.Pattern = "^([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?$";
break;
case EdmPrimitiveTypeKind.Geography:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.Geography"
};
break;
case EdmPrimitiveTypeKind.GeographyPoint:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeographyPoint"
};
break;
case EdmPrimitiveTypeKind.GeographyLineString:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeographyLineString"
};
break;
case EdmPrimitiveTypeKind.GeographyPolygon:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeographyPolygon"
};
break;
case EdmPrimitiveTypeKind.GeographyCollection:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeographyCollection"
};
break;
case EdmPrimitiveTypeKind.GeographyMultiPolygon:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeographyMultiPolygon"
};
break;
case EdmPrimitiveTypeKind.GeographyMultiLineString:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeographyMultiLineString"
};
break;
case EdmPrimitiveTypeKind.GeographyMultiPoint:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeographyMultiPoint"
};
break;
case EdmPrimitiveTypeKind.Geometry: // Geometry
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.Geometry"
};
break;
case EdmPrimitiveTypeKind.GeometryPoint:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeometryPoint"
};
break;
case EdmPrimitiveTypeKind.GeometryLineString:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeometryLineString"
};
break;
case EdmPrimitiveTypeKind.GeometryPolygon:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeometryPolygon"
};
break;
case EdmPrimitiveTypeKind.GeometryCollection:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeometryCollection"
};
break;
case EdmPrimitiveTypeKind.GeometryMultiPolygon:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeometryMultiPolygon"
};
break;
case EdmPrimitiveTypeKind.GeometryMultiLineString:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeometryMultiLineString"
};
break;
case EdmPrimitiveTypeKind.GeometryMultiPoint:
schema.Reference = new OpenApiReference
{
ExternalResource = _externalResource,
Id = "definitions/Edm.GeometryMultiPoint"
};
break;
case EdmPrimitiveTypeKind.None:
default:
throw new OpenApiException("Not supported primitive type.");
}
return schema;
}
public static void AppendODataErrors(this IDictionary<string, OpenApiSchema> schemas)
{
if (schemas == null)
{
return;
}
// odata.error
schemas.Add("odata.error", new OpenApiSchema
{
Type = "object",
Required = new List<string>
{
"error"
},
Properties = new Dictionary<string, OpenApiSchema>
{
{
"error",
new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = "odata.error.main"
}
}
}
}
});
// odata.error.main
schemas.Add("odata.error.main", new OpenApiSchema
{
Type = "object",
Required = new List<string>
{
"code", "message"
},
Properties = new Dictionary<string, OpenApiSchema>
{
{
"code", new OpenApiSchema { Type = "string" }
},
{
"message", new OpenApiSchema { Type = "string" }
},
{
"target", new OpenApiSchema { Type = "string" }
},
{
"details",
new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = "odata.error.detail"
}
}
}
},
{
"innererror",
new OpenApiSchema
{
Type = "object",
Description = "The structure of this object is service-specific"
}
}
}
});
// odata.error.detail
schemas.Add("odata.error.detail", new OpenApiSchema
{
Type = "object",
Required = new List<string>
{
"code", "message"
},
Properties = new Dictionary<string, OpenApiSchema>
{
{
"code", new OpenApiSchema { Type = "string" }
},
{
"message", new OpenApiSchema { Type = "string" }
},
{
"target", new OpenApiSchema { Type = "string" }
}
}
});
}
private static IOpenApiAny CreateDefault(IEdmStructuralProperty property)
{
if (property == null ||
property.DefaultValueString == null ||
!property.Type.IsPrimitive())
{
return null;
}
IEdmPrimitiveTypeReference primitiveTypeReference = property.Type.AsPrimitive();
switch (primitiveTypeReference.PrimitiveKind())
{
case EdmPrimitiveTypeKind.Boolean:
{
bool result;
if (Boolean.TryParse(property.DefaultValueString, out result))
{
return new OpenApiBoolean(result);
}
}
break;
case EdmPrimitiveTypeKind.Int16:
case EdmPrimitiveTypeKind.Int32:
{
int result;
if (Int32.TryParse(property.DefaultValueString, out result))
{
return new OpenApiInteger(result);
}
}
break;
case EdmPrimitiveTypeKind.Int64:
break;
// The type 'System.Double' is not supported in Open API document.
case EdmPrimitiveTypeKind.Double:
/*
{
double result;
if (Double.TryParse(property.DefaultValueString, out result))
{
return new OpenApiDouble((float)result);
}
}*/
break;
}
return new OpenApiString(property.DefaultValueString);
}
}
}