change to name generator and make the build pass

This commit is contained in:
Sam Xu 2017-11-14 14:18:27 -08:00
parent cfd65654d8
commit a39c8bf6b5
18 changed files with 1172 additions and 1116 deletions

View file

@ -7,6 +7,7 @@
using System;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Generators;
namespace Microsoft.OpenApi.OData
{
@ -16,16 +17,6 @@ namespace Microsoft.OpenApi.OData
/// </summary>
public static class EdmModelOpenApiMappingExtensions
{
/// <summary>
/// Convert <see cref="IEdmModel"/> to <see cref="OpenApiDocument"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The converted Open API document object.</returns>
public static OpenApiDocument Convert(this IEdmModel model)
{
return new OpenApiDocumentGenerator(model).Generate();
}
/// <summary>
/// Convert <see cref="IEdmModel"/> to <see cref="OpenApiDocument"/> using a configure action.
/// </summary>
@ -34,7 +25,36 @@ namespace Microsoft.OpenApi.OData
/// <returns>The converted Open API document object.</returns>
public static OpenApiDocument Convert(this IEdmModel model, Action<OpenApiDocument> configure)
{
return new OpenApiDocumentGenerator(model, configure).Generate();
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
if (configure == null)
{
throw Error.ArgumentNull(nameof(configure));
}
OpenApiDocument document = model.CreateDocument();
configure(document);
return document;
}
/// <summary>
/// Convert <see cref="IEdmModel"/> to <see cref="OpenApiDocument"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The converted Open API document object.</returns>
public static OpenApiDocument Convert(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
return model.CreateDocument();
}
}
}

View file

@ -0,0 +1,52 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiComponents"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiComponentsGenerator
{
/// <summary>
/// Generate the <see cref="OpenApiComponents"/>.
/// The value of components is a Components Object.
/// It holds maps of reusable schemas describing message bodies, operation parameters, and responses.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The components object.</returns>
public static OpenApiComponents CreateComponents(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
// "components": {
// "schemas": …,
// "parameters": …,
// "responses": …
// }
return new OpenApiComponents
{
// The value of schemas is a map of Schema Objects.
// Each entity type, complex type, enumeration type, and type definition directly
// or indirectly used in the paths field is represented as a name/value pair of the schemas map.
Schemas = model.CreateSchemas(),
// The value of parameters is a map of Parameter Objects.
// It allows defining query options and headers that can be reused across operations of the service.
Parameters = model.CreateParameters(),
// The value of responses is a map of Response Objects.
// It allows defining responses that can be reused across operations of the service.
Responses = model.CreateResponses()
};
}
}
}

View file

@ -0,0 +1,54 @@
// ------------------------------------------------------------
// 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 Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiDocument"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiDocumentGenerator
{
/// <summary>
/// Create a <see cref="OpenApiDocument"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The <see cref="OpenApiDocument"/> object.</returns>
public static OpenApiDocument CreateDocument(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
// An OAS document consists of a single OpenAPI Object represented as OpenApiDocument object.
// {
// "openapi":"3.0.0",
// "info": …,
// "servers": …,
// "tags": …,
// "paths": …,
// "components": …
// }
return new OpenApiDocument
{
SpecVersion = new Version(3, 0, 0),
Info = model.CreateInfo(),
Servers = model.CreateServers(),
Paths = model.CreatePaths(),
Components = model.CreateComponents(),
Tags = model.CreateTags()
};
}
}
}

View file

@ -0,0 +1,50 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiInfo"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiInfoGenerator
{
/// <summary>
/// Create <see cref="OpenApiInfo"/> object.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The info object.</returns>
public static OpenApiInfo CreateInfo(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
// The value of info is an Info Object,
// It contains the fields title and version, and optionally the field description.
return new OpenApiInfo
{
// The value of title is the value of the unqualified annotation Core.Description
// of the main schema or the entity container of the OData service.
// If no Core.Description is present, a default title has to be provided as this is a required OpenAPI field.
Title = "OData Service for namespace " + model.DeclaredNamespaces.FirstOrDefault(),
// The value of version is the value of the annotation Core.SchemaVersion(see[OData - VocCore]) of the main schema.
// If no Core.SchemaVersion is present, a default version has to be provided as this is a required OpenAPI field.
Version = "0.1.0",
// The value of description is the value of the annotation Core.LongDescription
// of the main schema or the entity container.
// While this field is optional, it prominently appears in OpenAPI exploration tools,
// so a default description should be provided if no Core.LongDescription annotation is present.
// Description = "This OData service is located at " + Settings.BaseUri?.OriginalString
};
}
}
}

View file

@ -1,25 +1,34 @@
//---------------------------------------------------------------------
// <copyright file="EdmNavigationSourceExtensions.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------
// ------------------------------------------------------------
// 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 System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Entension methods for navigation source
/// Extension methods to create <see cref="OpenApiOperation"/> by Edm elements.
/// </summary>
internal static class EdmNavigationSourceExtensions
internal static class OpenApiOperationGenerator
{
/// <summary>
/// The Path Item Object for the entity set contains the keyword get with an Operation Object as value
/// that describes the capabilities for querying the entity set.
/// </summary>
/// <param name="entitySet">The entity set.</param>
/// <returns>The created <see cref="OpenApiOperation"/>.</returns>
public static OpenApiOperation CreateGetOperationForEntitySet(this IEdmEntitySet entitySet)
{
if (entitySet == null)
{
throw Error.ArgumentNull(nameof(entitySet));
}
OpenApiOperation operation = new OpenApiOperation
{
Summary = "Get entities from " + entitySet.Name,
@ -32,36 +41,45 @@ namespace Microsoft.OpenApi.OData
}
};
// The parameters array contains Parameter Objects for system query options allowed for this entity set,
// and it does not list system query options not allowed for this entity set.
operation.Parameters = new List<OpenApiParameter>
{
new OpenApiParameter
{
Pointer = new OpenApiReference("#/components/parameters/top")
Pointer = new OpenApiReference(ReferenceType.Parameter, "top")
},
new OpenApiParameter
{
Pointer = new OpenApiReference("#/components/parameters/skip")
Pointer = new OpenApiReference(ReferenceType.Parameter, "skip")
},
new OpenApiParameter
{
Pointer = new OpenApiReference("#/components/parameters/search")
Pointer = new OpenApiReference(ReferenceType.Parameter, "search")
},
new OpenApiParameter
{
Pointer = new OpenApiReference("#/components/parameters/filter")
Pointer = new OpenApiReference(ReferenceType.Parameter, "filter")
},
new OpenApiParameter
{
Pointer = new OpenApiReference("#/components/parameters/count")
Pointer = new OpenApiReference(ReferenceType.Parameter, "count")
},
CreateOrderByParameter(entitySet),
// the syntax of the system query options $expand, $select, and $orderby is too flexible
// to be formally described with OpenAPI Specification means, yet the typical use cases
// of just providing a comma-separated list of properties can be expressed via an array-valued
// parameter with an enum constraint
entitySet.CreateOrderByParameter(),
CreateSelectParameter(entitySet),
entitySet.CreateSelectParameter(),
CreateExpandParameter(entitySet),
entitySet.CreateExpandParameter(),
};
// The value of responses is a Responses Object.
// It contains a name/value pair for the success case (HTTP response code 200)
// describing the structure of a successful response referencing the schema of the entity sets entity type in the global schemas
operation.Responses = new OpenApiResponses
{
{
@ -88,7 +106,7 @@ namespace Microsoft.OpenApi.OData
Type = "array",
Items = new OpenApiSchema
{
Pointer = new OpenApiReference("#/components/schemas/" + entitySet.EntityType().FullName())
Pointer = new OpenApiReference(ReferenceType.Schema, entitySet.EntityType().FullName())
}
}
}
@ -205,11 +223,11 @@ namespace Microsoft.OpenApi.OData
}
};
operation.Parameters = CreateKeyParameters(entitySet.EntityType());
operation.Parameters = entitySet.EntityType().CreateKeyParameters();
operation.Parameters.Add(CreateSelectParameter(entitySet));
operation.Parameters.Add(entitySet.CreateSelectParameter());
operation.Parameters.Add(CreateExpandParameter(entitySet));
operation.Parameters.Add(entitySet.CreateExpandParameter());
operation.Responses = new OpenApiResponses
{
@ -226,7 +244,7 @@ namespace Microsoft.OpenApi.OData
{
Schema = new OpenApiSchema
{
Pointer = new OpenApiReference("#/components/schemas/" + entitySet.EntityType().FullName())
Pointer = new OpenApiReference(ReferenceType.Schema, entitySet.EntityType().FullName())
}
}
}
@ -254,9 +272,9 @@ namespace Microsoft.OpenApi.OData
};
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(CreateSelectParameter(singleton));
operation.Parameters.Add(singleton.CreateSelectParameter());
operation.Parameters.Add(CreateExpandParameter(singleton));
operation.Parameters.Add(singleton.CreateExpandParameter());
operation.Responses = new OpenApiResponses
{
@ -273,7 +291,7 @@ namespace Microsoft.OpenApi.OData
{
Schema = new OpenApiSchema
{
Pointer = new OpenApiReference("#/components/schemas/" + singleton.EntityType().FullName())
Pointer = new OpenApiReference(ReferenceType.Schema, singleton.EntityType().FullName())
}
}
}
@ -300,7 +318,7 @@ namespace Microsoft.OpenApi.OData
}
};
operation.Parameters = CreateKeyParameters(entitySet.EntityType());
operation.Parameters = entitySet.EntityType().CreateKeyParameters();
operation.RequestBody = new OpenApiRequestBody
{
@ -313,7 +331,7 @@ namespace Microsoft.OpenApi.OData
{
Schema = new OpenApiSchema
{
Pointer = new OpenApiReference("#/components/schemas/" + entitySet.EntityType().FullName())
Pointer = new OpenApiReference(ReferenceType.Schema, entitySet.EntityType().FullName())
}
}
}
@ -353,7 +371,7 @@ namespace Microsoft.OpenApi.OData
{
Schema = new OpenApiSchema
{
Pointer = new OpenApiReference("#/components/schemas/" + singleton.EntityType().FullName())
Pointer = new OpenApiReference(ReferenceType.Schema, singleton.EntityType().FullName())
}
}
}
@ -381,7 +399,7 @@ namespace Microsoft.OpenApi.OData
}
}
};
operation.Parameters = CreateKeyParameters(entitySet.EntityType());
operation.Parameters = entitySet.EntityType().CreateKeyParameters();
operation.Parameters.Add(new OpenApiParameter
{
Name = "If-Match",
@ -400,135 +418,5 @@ namespace Microsoft.OpenApi.OData
};
return operation;
}
public static OpenApiParameter CreateOrderByParameter(this IEdmEntitySet entitySet)
{
OpenApiParameter parameter = new OpenApiParameter
{
Name = "$orderby",
In = ParameterLocation.Query,
Description = "Order items by property values",
Schema = new OpenApiSchema
{
Type = "array",
UniqueItems = true,
Items = new OpenApiSchema
{
Type = "string",
Enum = CreateOrderbyItems(entitySet)
}
}
};
return parameter;
}
public static IList<IOpenApiAny> CreateOrderbyItems(this IEdmEntitySet entitySet)
{
IList<IOpenApiAny> orderByItems = new List<IOpenApiAny>();
IEdmEntityType entityType = entitySet.EntityType();
foreach (var property in entityType.StructuralProperties())
{
orderByItems.Add(new OpenApiString(property.Name));
orderByItems.Add(new OpenApiString(property.Name + " desc"));
}
return orderByItems;
}
public static OpenApiParameter CreateSelectParameter(this IEdmNavigationSource navigationSource)
{
OpenApiParameter parameter = new OpenApiParameter
{
Name = "$select",
In = ParameterLocation.Query,
Description = "Select properties to be returned",
Schema = new OpenApiSchema
{
Type = "array",
UniqueItems = true,
Items = new OpenApiSchema
{
Type = "string",
Enum = CreateSelectItems(navigationSource.EntityType())
}
}
};
return parameter;
}
public static IList<IOpenApiAny> CreateSelectItems(this IEdmEntityType entityType)
{
IList<IOpenApiAny> selectItems = new List<IOpenApiAny>();
foreach (var property in entityType.StructuralProperties())
{
selectItems.Add(new OpenApiString(property.Name));
}
return selectItems;
}
public static OpenApiParameter CreateExpandParameter(this IEdmNavigationSource navigationSource)
{
OpenApiParameter parameter = new OpenApiParameter
{
Name = "$expand",
In = ParameterLocation.Query,
Description = "Expand related entities",
Schema = new OpenApiSchema
{
Type = "array",
UniqueItems = true,
Items = new OpenApiSchema
{
Type = "string",
Enum = CreateExpandItems(navigationSource.EntityType())
}
}
};
return parameter;
}
public static IList<IOpenApiAny> CreateExpandItems(this IEdmEntityType entityType)
{
IList<IOpenApiAny> expandItems = new List<IOpenApiAny>
{
new OpenApiString("*")
};
foreach (var property in entityType.NavigationProperties())
{
expandItems.Add(new OpenApiString(property.Name));
}
return expandItems;
}
public static IList<OpenApiParameter> CreateKeyParameters(this IEdmEntityType entityType)
{
IList<OpenApiParameter> parameters = new List<OpenApiParameter>();
// append key parameter
foreach (var keyProperty in entityType.Key())
{
OpenApiParameter parameter = new OpenApiParameter
{
Name = keyProperty.Name,
In = ParameterLocation.Path,
Required = true,
Description = "key: " + keyProperty.Name,
Schema = keyProperty.Type.CreateSchema()
};
parameters.Add(parameter);
}
return parameters;
}
}
}

View file

@ -0,0 +1,284 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiParameter"/>.
/// </summary>
internal static class OpenApiParameterGenerator
{
/// <summary>
/// Create key parameters for the <see cref="IEdmEntityType"/>.
/// </summary>
/// <param name="entityType">The entity type.</param>
/// <returns>The created the list of <see cref="OpenApiParameter"/>.</returns>
public static IList<OpenApiParameter> CreateKeyParameters(this IEdmEntityType entityType)
{
if (entityType == null)
{
throw Error.ArgumentNull(nameof(entityType));
}
IList<OpenApiParameter> parameters = new List<OpenApiParameter>();
// append key parameter
foreach (var keyProperty in entityType.Key())
{
OpenApiParameter parameter = new OpenApiParameter
{
Name = keyProperty.Name,
In = ParameterLocation.Path,
Required = true,
Description = "key: " + keyProperty.Name,
Schema = keyProperty.Type.CreateSchema()
};
parameters.Add(parameter);
}
return parameters;
}
/// <summary>
/// Create $orderby parameter for the <see cref="IEdmEntitySet"/>.
/// </summary>
/// <param name="entitySet">The entity set.</param>
/// <returns>The created <see cref="OpenApiParameter"/>.</returns>
public static OpenApiParameter CreateOrderByParameter(this IEdmEntitySet entitySet)
{
if (entitySet == null)
{
throw Error.ArgumentNull(nameof(entitySet));
}
OpenApiParameter parameter = new OpenApiParameter
{
Name = "$orderby",
In = ParameterLocation.Query,
Description = "Order items by property values",
Schema = new OpenApiSchema
{
Type = "array",
UniqueItems = true,
Items = new OpenApiSchema
{
Type = "string",
Enum = VisitOrderbyItems(entitySet.EntityType())
}
}
};
return parameter;
}
/// <summary>
/// Create $select parameter for the <see cref="IEdmNavigationSource"/>.
/// </summary>
/// <param name="navigationSource">The navigation source.</param>
/// <returns>The created <see cref="OpenApiParameter"/>.</returns>
public static OpenApiParameter CreateSelectParameter(this IEdmNavigationSource navigationSource)
{
if (navigationSource == null)
{
throw Error.ArgumentNull(nameof(navigationSource));
}
OpenApiParameter parameter = new OpenApiParameter
{
Name = "$select",
In = ParameterLocation.Query,
Description = "Select properties to be returned",
Schema = new OpenApiSchema
{
Type = "array",
UniqueItems = true,
Items = new OpenApiSchema
{
Type = "string",
Enum = VisitSelectItems(navigationSource.EntityType())
}
}
};
return parameter;
}
/// <summary>
/// Create $expand parameter for the <see cref="IEdmNavigationSource"/>.
/// </summary>
/// <param name="navigationSource">The navigation source.</param>
/// <returns>The created <see cref="OpenApiParameter"/>.</returns>
public static OpenApiParameter CreateExpandParameter(this IEdmNavigationSource navigationSource)
{
if (navigationSource == null)
{
throw Error.ArgumentNull(nameof(navigationSource));
}
OpenApiParameter parameter = new OpenApiParameter
{
Name = "$expand",
In = ParameterLocation.Query,
Description = "Expand related entities",
Schema = new OpenApiSchema
{
Type = "array",
UniqueItems = true,
Items = new OpenApiSchema
{
Type = "string",
Enum = VisitExpandItems(navigationSource.EntityType())
}
}
};
return parameter;
}
// 4.6.2 Field parameters in components
public static IDictionary<string, OpenApiParameter> CreateParameters(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
// It allows defining query options and headers that can be reused across operations of the service.
// The value of parameters is a map of Parameter Objects
return new Dictionary<string, OpenApiParameter>
{
{ "top", CreateTop() },
{ "skip", CreateSkip() },
{ "count", CreateCount() },
{ "filter", CreateFilter() },
{ "search", CreateSearch() },
};
}
// #top
private static OpenApiParameter CreateTop()
{
return new OpenApiParameter
{
Name = "$top",
In = ParameterLocation.Query,
Description = "Show only the first n items",
Schema = new OpenApiSchema
{
Type = "integer",
Minimum = 0,
},
Example = new OpenApiInteger(50)
};
}
// $skip
private static OpenApiParameter CreateSkip()
{
return new OpenApiParameter
{
Name = "$skip",
In = ParameterLocation.Query,
Description = "Skip the first n items",
Schema = new OpenApiSchema
{
Type = "integer",
Minimum = 0,
}
};
}
// $count
private static OpenApiParameter CreateCount()
{
return new OpenApiParameter
{
Name = "$count",
In = ParameterLocation.Query,
Description = "Include count of items",
Schema = new OpenApiSchema
{
Type = "boolean"
}
};
}
// $filter
private static OpenApiParameter CreateFilter()
{
return new OpenApiParameter
{
Name = "$filter",
In = ParameterLocation.Query,
Description = "Filter items by property values",
Schema = new OpenApiSchema
{
Type = "string"
}
};
}
// $search
private static OpenApiParameter CreateSearch()
{
return new OpenApiParameter
{
Name = "$search",
In = ParameterLocation.Query,
Description = "Search items by search phrases",
Schema = new OpenApiSchema
{
Type = "string"
}
};
}
private static IList<IOpenApiAny> VisitOrderbyItems(this IEdmEntityType entityType)
{
IList<IOpenApiAny> orderByItems = new List<IOpenApiAny>();
foreach (var property in entityType.StructuralProperties())
{
orderByItems.Add(new OpenApiString(property.Name));
orderByItems.Add(new OpenApiString(property.Name + " desc"));
}
return orderByItems;
}
private static IList<IOpenApiAny> VisitSelectItems(this IEdmEntityType entityType)
{
IList<IOpenApiAny> selectItems = new List<IOpenApiAny>();
foreach (var property in entityType.StructuralProperties())
{
selectItems.Add(new OpenApiString(property.Name));
}
return selectItems;
}
private static IList<IOpenApiAny> VisitExpandItems(this IEdmEntityType entityType)
{
IList<IOpenApiAny> expandItems = new List<IOpenApiAny>
{
new OpenApiString("*")
};
foreach (var property in entityType.NavigationProperties())
{
expandItems.Add(new OpenApiString(property.Name));
}
return expandItems;
}
}
}

View file

@ -1,38 +1,137 @@
//---------------------------------------------------------------------
// <copyright file="EdmElementOpenApiElementExtensions.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------
// ------------------------------------------------------------
// 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 System.Text;
using System.Linq;
using System.Text;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods for Edm Elements to Open Api Elements.
/// Extension methods to create <see cref="OpenApiPathItem"/> by Edm elements.
/// </summary>
internal static class EdmElementOpenApiElementExtensions
internal static class OpenApiPathItemGenerator
{
private static IDictionary<string, OpenApiResponse> Responses =
new Dictionary<string, OpenApiResponse>
{
{ "default",
new OpenApiResponse
{
Pointer = new OpenApiReference("#/components/responses/error")
}
},
{ "204", new OpenApiResponse { Description = "Success"} },
};
public static KeyValuePair<string, OpenApiResponse> GetResponse(this string statusCode)
/// <summary>
/// Path items for Entity Sets.
/// Each entity set is represented as a name/value pair
/// whose name is the service-relative resource path of the entity set prepended with a forward slash,
/// and whose value is a Path Item Object.
/// </summary>
/// <param name="entitySet">The Edm entity set.</param>
/// <returns>The path items.</returns>
public static IDictionary<string, OpenApiPathItem> CreatePathItems(this IEdmEntitySet entitySet)
{
return new KeyValuePair<string, OpenApiResponse>(statusCode, Responses[statusCode]);
if (entitySet == null)
{
throw Error.ArgumentNull(nameof(entitySet));
}
IDictionary<string, OpenApiPathItem> paths = new Dictionary<string, OpenApiPathItem>();
// entity set
OpenApiPathItem pathItem = new OpenApiPathItem();
pathItem.AddOperation(OperationType.Get, entitySet.CreateGetOperationForEntitySet());
pathItem.AddOperation(OperationType.Post, entitySet.CreatePostOperationForEntitySet());
paths.Add("/" + entitySet.Name, pathItem);
// entity
string entityPathName = entitySet.CreatePathNameForEntity();
pathItem = new OpenApiPathItem();
pathItem.AddOperation(OperationType.Get, entitySet.CreateGetOperationForEntity());
pathItem.AddOperation(OperationType.Patch, entitySet.CreatePatchOperationForEntity());
pathItem.AddOperation(OperationType.Delete, entitySet.CreateDeleteOperationForEntity());
paths.Add(entityPathName, pathItem);
return paths;
}
public static IDictionary<string, OpenApiPathItem> CreatePathItems(this IEdmSingleton singleton)
{
if (singleton == null)
{
throw Error.ArgumentNull(nameof(singleton));
}
IDictionary<string, OpenApiPathItem> paths = new Dictionary<string, OpenApiPathItem>();
// Singleton
string entityPathName = singleton.CreatePathNameForSingleton();
OpenApiPathItem pathItem = new OpenApiPathItem();
pathItem.AddOperation(OperationType.Get, singleton.CreateGetOperationForSingleton());
pathItem.AddOperation(OperationType.Patch, singleton.CreatePatchOperationForSingleton());
paths.Add(entityPathName, pathItem);
return paths;
}
public static IDictionary<string, OpenApiPathItem> CreateOperationPathItems(this IEdmNavigationSource navigationSource,
IDictionary<IEdmTypeReference, IEdmOperation> boundOperations)
{
IDictionary<string, OpenApiPathItem> operationPathItems = new Dictionary<string, OpenApiPathItem>();
IEnumerable<IEdmOperation> operations;
IEdmEntitySet entitySet = navigationSource as IEdmEntitySet;
// collection bound
if (entitySet != null)
{
operations = FindOperations(navigationSource.EntityType(), boundOperations, collection: true);
foreach (var operation in operations)
{
OpenApiPathItem openApiOperation = operation.CreatePathItem();
string operationPathName = operation.CreatePathItemName();
operationPathItems.Add("/" + navigationSource.Name + operationPathName, openApiOperation);
}
}
// non-collection bound
operations = FindOperations(navigationSource.EntityType(), boundOperations, collection: false);
foreach (var operation in operations)
{
OpenApiPathItem openApiOperation = operation.CreatePathItem();
string operationPathName = operation.CreatePathItemName();
string temp;
if (entitySet != null)
{
temp = entitySet.CreatePathNameForEntity();
}
else
{
temp = "/" + navigationSource.Name;
}
operationPathItems.Add(temp + operationPathName, openApiOperation);
}
return operationPathItems;
}
private static IEnumerable<IEdmOperation> FindOperations(IEdmEntityType entityType,
IDictionary<IEdmTypeReference, IEdmOperation> boundOperations,
bool collection)
{
string fullTypeName = collection ? "Collection(" + entityType.FullName() + ")" :
entityType.FullName();
foreach (var item in boundOperations)
{
if (item.Key.FullName() == fullTypeName)
{
yield return item.Value;
}
}
}
public static OpenApiPathItem CreatePathItem(this IEdmOperationImport operationImport)
@ -68,8 +167,8 @@ namespace Microsoft.OpenApi.OData
{
Summary = "Invoke action " + action.Name,
Tags = CreateTags(action),
Parameters = CreateParameters(action),
Responses = CreateResponses(action)
Parameters = action.CreateParameters(),
Responses = action.CreateResponses()
};
pathItem.AddOperation(OperationType.Post, post);
@ -88,8 +187,8 @@ namespace Microsoft.OpenApi.OData
{
Summary = "Invoke function " + function.Name,
Tags = CreateTags(function),
Parameters = CreateParameters(function),
Responses = CreateResponses(function)
Parameters = function.CreateParameters(),
Responses = function.CreateResponses()
};
pathItem.AddOperation(OperationType.Get, get);
@ -142,38 +241,6 @@ namespace Microsoft.OpenApi.OData
return ((IEdmFunction)operation).CreatePathItemName();
}
private static OpenApiResponses CreateResponses(this IEdmAction actionImport)
{
return new OpenApiResponses
{
"204".GetResponse(),
"default".GetResponse()
};
}
private static OpenApiResponses CreateResponses(this IEdmFunction function)
{
OpenApiResponses responses = new OpenApiResponses();
OpenApiResponse response = new OpenApiResponse
{
Description = "Success",
Content = new Dictionary<string, OpenApiMediaType>
{
{
"application/json",
new OpenApiMediaType
{
Schema = function.ReturnType.CreateSchema()
}
}
}
};
responses.Add("200", response);
responses.Add("default".GetResponse());
return responses;
}
private static IList<string> CreateTags(this IEdmOperationImport operationImport)
{
if (operationImport.EntitySet != null)

View file

@ -0,0 +1,108 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiPaths"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiPathsGenerator
{
/// <summary>
/// Create the <see cref="OpenApiPaths"/>
/// The value of paths is a Paths Object.
/// It is the main source of information on how to use the described API.
/// It consists of name/value pairs whose name is a path template relative to the service root URL,
/// and whose value is a Path Item Object.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>the paths object.</returns>
public static OpenApiPaths CreatePaths(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
// Due to the power and flexibility of OData a full representation of all service capabilities
// in the Paths Object is typically not feasible, so this mapping only describes the minimum
// information desired in the Paths Object.
OpenApiPaths paths = new OpenApiPaths();
if (model.EntityContainer != null)
{
IDictionary<IEdmTypeReference, IEdmOperation> boundOperations = new Dictionary<IEdmTypeReference, IEdmOperation>();
foreach (var edmOperation in model.SchemaElements.OfType<IEdmOperation>().Where(e => e.IsBound))
{
IEdmOperationParameter bindingParameter = edmOperation.Parameters.First();
boundOperations.Add(bindingParameter.Type, edmOperation);
}
foreach (var element in model.EntityContainer.Elements)
{
switch (element.ContainerElementKind)
{
case EdmContainerElementKind.EntitySet: // entity set
IEdmEntitySet entitySet = element as IEdmEntitySet;
if (entitySet != null)
{
foreach (var item in entitySet.CreatePathItems())
{
paths.Add(item.Key, item.Value);
}
foreach(var item in entitySet.CreateOperationPathItems(boundOperations))
{
paths.Add(item.Key, item.Value);
}
}
break;
case EdmContainerElementKind.Singleton: // singleton
IEdmSingleton singleton = element as IEdmSingleton;
if (singleton != null)
{
foreach (var item in singleton.CreatePathItems())
{
paths.Add(item.Key, item.Value);
}
foreach (var item in singleton.CreateOperationPathItems(boundOperations))
{
paths.Add(item.Key, item.Value);
}
}
break;
case EdmContainerElementKind.FunctionImport: // function import
IEdmFunctionImport functionImport = element as IEdmFunctionImport;
if (functionImport != null)
{
var functionImportPathItem = functionImport.CreatePathItem();
paths.Add(functionImport.CreatePathItemName(), functionImportPathItem);
}
break;
case EdmContainerElementKind.ActionImport: // action import
IEdmActionImport actionImport = element as IEdmActionImport;
if (actionImport != null)
{
var functionImportPathItem = actionImport.CreatePathItem();
paths.Add(actionImport.CreatePathItemName(), functionImportPathItem);
}
break;
}
}
}
return paths;
}
}
}

View file

@ -0,0 +1,107 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
using System.Collections.Generic;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiResponse"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiResponseGenerator
{
private static IDictionary<string, OpenApiResponse> Responses =
new Dictionary<string, OpenApiResponse>
{
{ "default",
new OpenApiResponse
{
Pointer = new OpenApiReference(ReferenceType.Response, "error")
}
},
{ "204", new OpenApiResponse { Description = "Success"} },
};
public static KeyValuePair<string, OpenApiResponse> GetResponse(this string statusCode)
{
return new KeyValuePair<string, OpenApiResponse>(statusCode, Responses[statusCode]);
}
/// <summary>
/// Create <see cref="OpenApiInfo"/> object.
/// It contains one name/value pair for the standard OData error response
/// that is referenced from all operations of the service.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The name/value pairs for the standard OData error response.</returns>
public static IDictionary<string, OpenApiResponse> CreateResponses(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
return new Dictionary<string, OpenApiResponse>
{
{ "error", CreateError() }
};
}
public static OpenApiResponses CreateResponses(this IEdmAction actionImport)
{
return new OpenApiResponses
{
"204".GetResponse(),
"default".GetResponse()
};
}
public static OpenApiResponses CreateResponses(this IEdmFunction function)
{
OpenApiResponses responses = new OpenApiResponses();
OpenApiResponse response = new OpenApiResponse
{
Description = "Success",
Content = new Dictionary<string, OpenApiMediaType>
{
{
"application/json",
new OpenApiMediaType
{
Schema = function.ReturnType.CreateSchema()
}
}
}
};
responses.Add("200", response);
responses.Add("default".GetResponse());
return responses;
}
private static OpenApiResponse CreateError()
{
return new OpenApiResponse
{
Description = "error",
Content = new Dictionary<string, OpenApiMediaType>
{
{
"application/json",
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Pointer = new OpenApiReference(ReferenceType.Schema, "odata.error")
}
}
}
}
};
}
}
}

View file

@ -0,0 +1,172 @@
// ------------------------------------------------------------
// 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.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiSchema"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiSchemaGenerator
{
/// <summary>
/// Create the dictionary of <see cref="OpenApiSchema"/> object.
/// The name of each pair is the namespace-qualified name of the type. It uses the namespace instead of the alias.
/// The value of each pair is a <see cref="OpenApiSchema"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The info object.</returns>
public static IDictionary<string, OpenApiSchema> CreateSchemas(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
IDictionary<string, OpenApiSchema> schemas = new Dictionary<string, OpenApiSchema>();
// Each entity type, complex type, enumeration type, and type definition directly
// or indirectly used in the paths field is represented as a name / value pair of the schemas map.
foreach (var element in model.SchemaElements)
{
switch (element.SchemaElementKind)
{
case EdmSchemaElementKind.TypeDefinition: // Type definition
{
IEdmType reference = (IEdmType)element;
schemas.Add(reference.FullTypeName(), VisitSchemaType(model, reference));
}
break;
}
}
schemas.AppendODataErrors();
return schemas;
}
private static OpenApiSchema VisitSchemaType(IEdmModel model, IEdmType definition)
{
switch (definition.TypeKind)
{
case EdmTypeKind.Complex: // complex type
case EdmTypeKind.Entity: // entity type
return VisitStructuredType((IEdmStructuredType)definition, true);
case EdmTypeKind.Enum: // enum type
return VisitEnumType(model, (IEdmEnumType)definition);
case EdmTypeKind.TypeDefinition: // type definition
return VisitTypeDefinitions((IEdmTypeDefinition)definition);
case EdmTypeKind.None:
default:
throw Error.NotSupported(String.Format("Not supported {0} type kind.", definition.TypeKind));
}
}
// 4.6.1.2 Schemas for Enumeration Types
private static OpenApiSchema VisitEnumType(IEdmModel model, IEdmEnumType enumType)
{
OpenApiSchema schema = new OpenApiSchema
{
// An enumeration type is represented as a Schema Object of type string
Type = "string",
// containing the OpenAPI Specification enum keyword.
Enum = new List<IOpenApiAny>(),
// It optionally can contain the field description,
// whose value is the value of the unqualified annotation Core.Description of the enumeration type.
Description = model.GetDescription(enumType)
};
// Enum value is an array that contains a string with the member name for each enumeration member.
foreach (IEdmEnumMember member in enumType.Members)
{
schema.Enum.Add(new OpenApiString(member.Name));
}
schema.Title = (enumType as IEdmSchemaElement)?.Name;
return schema;
}
// 4.6.1.3 Schemas for Type Definitions
private static OpenApiSchema VisitTypeDefinitions(IEdmTypeDefinition typeDefinition)
{
return typeDefinition?.UnderlyingType?.CreateSchema();
}
// 4.6.1.1 Schemas for Entity Types and Complex Types
private static OpenApiSchema VisitStructuredType(IEdmStructuredType structuredType, bool processBase)
{
if (processBase && structuredType.BaseType != null)
{
// A structured type with a base type is represented as a Schema Object
// that contains the keyword allOf whose value is an array with two items:
return new OpenApiSchema
{
AllOf = new List<OpenApiSchema>
{
// 1. a JSON Reference to the Schema Object of the base type
new OpenApiSchema
{
Pointer = new OpenApiReference(ReferenceType.Schema, structuredType.BaseType.FullTypeName())
},
// 2. a Schema Object describing the derived type
VisitStructuredType(structuredType, false)
}
};
}
else
{
// A structured type without a base type is represented as a Schema Object of type object
return new OpenApiSchema
{
Title = (structuredType as IEdmSchemaElement).Name,
Type = "object",
// Each structural property and navigation property is represented
// as a name/value pair of the standard OpenAPI properties object.
Properties = VisitStructuredTypeProperties(structuredType),
// It optionally can contain the field description,
// whose value is the value of the unqualified annotation Core.Description of the structured type.
// However, ODL doesn't support the Core.Description on structure type.
};
}
}
// 4.6.1.1 Properties
private static IDictionary<string, OpenApiSchema> VisitStructuredTypeProperties(IEdmStructuredType structuredType)
{
// The name is the property name, the value is a Schema Object describing the allowed values of the property.
IDictionary<string, OpenApiSchema> properties = new Dictionary<string, OpenApiSchema>();
// structure properties
foreach (var property in structuredType.DeclaredStructuralProperties())
{
OpenApiSchema propertySchema = property.Type.CreateSchema();
propertySchema.Default = property.DefaultValueString != null ? new OpenApiString(property.DefaultValueString) : null;
properties.Add(property.Name, propertySchema);
}
// navigation properties
foreach (var property in structuredType.DeclaredNavigationProperties())
{
OpenApiSchema propertySchema = property.Type.CreateSchema();
properties.Add(property.Name, propertySchema);
}
return properties;
}
}
}

View file

@ -0,0 +1,41 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiServer"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiServersGenerator
{
/// <summary>
/// Create the collection of <see cref="OpenApiServer"/> object.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The servers object.</returns>
public static IList<OpenApiServer> CreateServers(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
// The value of servers is an array of Server Objects.
// It contains one object with a field url.
// The value of url is a string cotaining the service root URL without the trailing forward slash.
return new List<OpenApiServer>
{
new OpenApiServer
{
Url = "http://localhost"
}
};
}
}
}

View file

@ -0,0 +1,72 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// Extension methods to create <see cref="OpenApiTag"/> by <see cref="IEdmModel"/>.
/// </summary>
internal static class OpenApiTagsGenerator
{
/// <summary>
/// Create the collection of <see cref="OpenApiTag"/> object.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <returns>The collection of <see cref="OpenApiTag"/> object.</returns>
public static IList<OpenApiTag> CreateTags(this IEdmModel model)
{
if (model == null)
{
throw Error.ArgumentNull(nameof(model));
}
// The value of tags is an array of Tag Objects.
// For an OData service the natural groups are entity sets and singletons,
// so the tags array contains one Tag Object per entity set and singleton in the entity container.
IList<OpenApiTag> tags = new List<OpenApiTag>();
if (model.EntityContainer != null)
{
foreach (IEdmEntityContainerElement element in model.EntityContainer.Elements)
{
switch (element.ContainerElementKind)
{
case EdmContainerElementKind.EntitySet: // entity set
IEdmEntitySet entitySet = (IEdmEntitySet)element;
tags.Add(new OpenApiTag
{
Name = entitySet.Name,
Description = model.GetDescription(entitySet)
});
break;
case EdmContainerElementKind.Singleton: // singleton
IEdmSingleton singleton = (IEdmSingleton)element;
tags.Add(new OpenApiTag
{
Name = singleton.Name,
Description = model.GetDescription(singleton)
});
break;
// The tags array can contain additional Tag Objects for other logical groups,
// e.g. for action imports or function imports that are not associated with an entity set.
case EdmContainerElementKind.ActionImport: // Action Import
break;
case EdmContainerElementKind.FunctionImport: // Function Import
break;
}
}
}
// Tags is optional.
return tags.Any() ? tags : null;
}
}
}

View file

@ -9,7 +9,7 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OData.Edm;
namespace Microsoft.OpenApi.OData.Schema
namespace Microsoft.OpenApi.OData.Generators
{
/// <summary>
/// See https://github.com/oasis-tcs/odata-openapi/blob/master/examples/odata-definitions.json
@ -285,13 +285,21 @@ namespace Microsoft.OpenApi.OData.Schema
return schema;
}
private static void InitErrors()
public static void AppendODataErrors(this IDictionary<string, OpenApiSchema> schemas)
{
_errors = new Dictionary<string, OpenApiSchema>();
if (schemas == null)
{
return;
}
_errors.Add("odata.error", new OpenApiSchema
// odata.error
schemas.Add("odata.error", new OpenApiSchema
{
Type = "object",
Required = new List<string>
{
"error"
},
Properties = new Dictionary<string, OpenApiSchema>
{
{
@ -304,31 +312,24 @@ namespace Microsoft.OpenApi.OData.Schema
}
});
_errors.Add("odata.error.main", new OpenApiSchema
// odata.error.main
schemas.Add("odata.error.main", new OpenApiSchema
{
Type = SchemaType.Object.GetMetadataName(),
Type = "object",
Required = new List<string>
{
"code", "message"
},
Properties = new Dictionary<string, OpenApiSchema>
{
{
"code",
new OpenApiSchema
{
Type = "string"
}
"code", new OpenApiSchema { Type = "string" }
},
{
"message",
new OpenApiSchema
{
Type = "string"
}
"message", new OpenApiSchema { Type = "string" }
},
{
"target",
new OpenApiSchema
{
Type = "string"
}
"target", new OpenApiSchema { Type = "string" }
},
{
"details",
@ -352,31 +353,20 @@ namespace Microsoft.OpenApi.OData.Schema
}
});
_errors.Add("odata.error.detail", new OpenApiSchema
// odata.error.detail
schemas.Add("odata.error.detail", new OpenApiSchema
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
{
"code",
new OpenApiSchema
{
Type = "string"
}
"code", new OpenApiSchema { Type = "string" }
},
{
"message",
new OpenApiSchema
{
Type = "string"
}
"message", new OpenApiSchema { Type = "string" }
},
{
"target",
new OpenApiSchema
{
Type = "string"
}
"target", new OpenApiSchema { Type = "string" }
}
}
});

View file

@ -20,7 +20,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Schema\SchemaExtensions.cs" />
<Compile Remove="EdmElementOpenApiElementExtensions.cs" />
</ItemGroup>
<ItemGroup>

View file

@ -1,406 +0,0 @@
//---------------------------------------------------------------------
// <copyright file="EdmOpenApiComponentsGenerator.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Schema;
namespace Microsoft.OpenApi.OData
{
/// <summary>
/// Visit Edm model to generate <see cref="OpenApiComponents"/>
/// </summary>
internal class OpenApiComponentsGenerator
{
private IEdmModel _model;
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiComponentsGenerator" /> class.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The Open Api writer settings.</param>
public OpenApiComponentsGenerator(IEdmModel model)
{
_model = model ?? throw Error.ArgumentNull(nameof(model));
}
/// <summary>
/// Generate the <see cref="OpenApiComponents"/>
/// </summary>
/// <returns>the components object.</returns>
public OpenApiComponents Generate()
{
// The value of components is a Components Object.
// It holds maps of reusable schemas describing message bodies, operation parameters, and responses.
return new OpenApiComponents
{
// The value of schemas is a map of Schema Objects.
// Each entity type, complex type, enumeration type, and type definition directly
// or indirectly used in the paths field is represented as a name/value pair of the schemas map.
Schemas = VisitSchemas(),
// The value of parameters is a map of Parameter Objects.
// It allows defining query options and headers that can be reused across operations of the service.
Parameters = VisitParameters(),
// The value of responses is a map of Response Objects.
// It allows defining responses that can be reused across operations of the service.
Responses = VisitResponses()
};
}
/// <summary>
/// Visit the Edm schema and generate the <see cref="OpenApiSchema"/>.
/// The name of each pair is the namespace-qualified name of the type. It uses the namespace instead of the alias.
/// The value of each pair is a <see cref="OpenApiSchema"/>.
/// </summary>
/// <returns></returns>
private IDictionary<string, OpenApiSchema> VisitSchemas()
{
IDictionary<string, OpenApiSchema> schemas = new Dictionary<string, OpenApiSchema>();
foreach (var element in _model.SchemaElements)
{
switch (element.SchemaElementKind)
{
case EdmSchemaElementKind.TypeDefinition:
{
IEdmType reference = (IEdmType)element;
schemas.Add(reference.FullTypeName(), VisitSchemaType(reference));
}
break;
}
}
AppendODataErrors(schemas);
return schemas;
}
private OpenApiSchema VisitSchemaType(IEdmType definition)
{
switch (definition.TypeKind)
{
case EdmTypeKind.Complex:
case EdmTypeKind.Entity:
return VisitStructuredType((IEdmStructuredType)definition, true);
case EdmTypeKind.Enum: // enum type
return VisitEnumType((IEdmEnumType)definition);
case EdmTypeKind.TypeDefinition:
return VisitTypeDefinitions((IEdmTypeDefinition)definition);
case EdmTypeKind.None:
default:
throw Error.NotSupported(String.Format("Not supported {0} type kind.", definition.TypeKind));
}
}
// 4.6.1.2 Schemas for Enumeration Types
private OpenApiSchema VisitEnumType(IEdmEnumType enumType)
{
OpenApiSchema schema = new OpenApiSchema
{
// An enumeration type is represented as a Schema Object of type string
Type = "string",
// containing the OpenAPI Specification enum keyword.
Enum = new List<IOpenApiAny>(),
// It optionally can contain the field description,
// whose value is the value of the unqualified annotation Core.Description of the enumeration type.
Description = _model.GetDescription(enumType)
};
// Enum value is an array that contains a string with the member name for each enumeration member.
foreach (IEdmEnumMember member in enumType.Members)
{
schema.Enum.Add(new OpenApiString(member.Name));
}
schema.Title = (enumType as IEdmSchemaElement)?.Name;
return schema;
}
// 4.6.1.3 Schemas for Type Definitions
private OpenApiSchema VisitTypeDefinitions(IEdmTypeDefinition typeDefinition)
{
return typeDefinition?.UnderlyingType?.CreateSchema();
}
// 4.6.1.1 Schemas for Entity Types and Complex Types
private OpenApiSchema VisitStructuredType(IEdmStructuredType structuredType, bool processBase)
{
if (processBase && structuredType.BaseType != null)
{
// A structured type with a base type is represented as a Schema Object
// that contains the keyword allOf whose value is an array with two items:
return new OpenApiSchema
{
AllOf = new List<OpenApiSchema>
{
// 1. a JSON Reference to the Schema Object of the base type
new OpenApiSchema
{
Pointer = new OpenApiReference(ReferenceType.Schema, structuredType.BaseType.FullTypeName())
},
// 2. a Schema Object describing the derived type
VisitStructuredType(structuredType, false)
}
};
}
else
{
// A structured type without a base type is represented as a Schema Object of type object
return new OpenApiSchema
{
Title = (structuredType as IEdmSchemaElement).Name,
Type = "object",
// Each structural property and navigation property is represented
// as a name/value pair of the standard OpenAPI properties object.
Properties = VisitStructuredTypeProperties(structuredType),
// It optionally can contain the field description,
// whose value is the value of the unqualified annotation Core.Description of the structured type.
// However, ODL doesn't support the Core.Description on structure type.
};
}
}
// 4.6.1.1 Properties
private IDictionary<string, OpenApiSchema> VisitStructuredTypeProperties(IEdmStructuredType structuredType)
{
// The name is the property name, the value is a Schema Object describing the allowed values of the property.
IDictionary<string, OpenApiSchema> properties = new Dictionary<string, OpenApiSchema>();
// structure properties
foreach (var property in structuredType.DeclaredStructuralProperties())
{
OpenApiSchema propertySchema = property.Type.CreateSchema();
propertySchema.Default = property.DefaultValueString != null ? new OpenApiString(property.DefaultValueString) : null;
properties.Add(property.Name, propertySchema);
}
// navigation properties
foreach (var property in structuredType.DeclaredNavigationProperties())
{
OpenApiSchema propertySchema = property.Type.CreateSchema();
properties.Add(property.Name, propertySchema);
}
return properties;
}
private IDictionary<string, OpenApiParameter> VisitParameters()
{
return new Dictionary<string, OpenApiParameter>
{
{ "top", VisitTop() },
{ "skip", VisitSkip() },
{ "count", VisitCount() },
{ "filter", VisitFilter() },
{ "search", VisitSearch() },
};
}
private OpenApiParameter VisitTop()
{
return new OpenApiParameter
{
Name = "$top",
In = ParameterLocation.Query,
Description = "Show only the first n items",
Schema = new OpenApiSchema
{
Type = "integer",
Minimum = 0,
},
Example = new OpenApiInteger(50)
};
}
private OpenApiParameter VisitSkip()
{
return new OpenApiParameter
{
Name = "$skip",
In = ParameterLocation.Query,
Description = "Skip only the first n items",
Schema = new OpenApiSchema
{
Type = "integer",
Minimum = 0,
}
};
}
private OpenApiParameter VisitCount()
{
return new OpenApiParameter
{
Name = "$count",
In = ParameterLocation.Query,
Description = "Include count of items",
Schema = new OpenApiSchema
{
Type = "boolean"
}
};
}
private OpenApiParameter VisitFilter()
{
return new OpenApiParameter
{
Name = "$filter",
In = ParameterLocation.Query,
Description = "Filter items by property values",
Schema = new OpenApiSchema
{
Type = "string"
}
};
}
private OpenApiParameter VisitSearch()
{
return new OpenApiParameter
{
Name = "$search",
In = ParameterLocation.Query,
Description = "Search items by search phrases",
Schema = new OpenApiSchema
{
Type = "string"
}
};
}
private void AppendODataErrors(IDictionary<string, OpenApiSchema> schemas)
{
schemas.Add("odata.error", new OpenApiSchema
{
Type = "object",
Required = new List<string>
{
"error"
},
Properties = new Dictionary<string, OpenApiSchema>
{
{
"error",
new OpenApiSchema
{
Pointer = new OpenApiReference("#/components/schemas/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
{
Pointer = new OpenApiReference("#/components/schemas/odata.error.detail")
}
}
},
{
"innererror",
new OpenApiSchema
{
Type = "object",
Description = "The structure of this object is service-specific"
}
}
}
});
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" }
}
}
});
}
/// <summary>
/// It contains one name/value pair for the standard OData error response
/// that is referenced from all operations of the service.
/// </summary>
/// <returns>Teh name/value pairs for the standard OData error response</returns>
private IDictionary<string, OpenApiResponse> VisitResponses()
{
return new Dictionary<string, OpenApiResponse>
{
{ "error", VisitError() }
};
}
private OpenApiResponse VisitError()
{
return new OpenApiResponse
{
Description = "error",
Content = new Dictionary<string, OpenApiMediaType>
{
{
"application/json",
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Pointer = new OpenApiReference("#/components/schemas/odata.error")
}
}
}
}
};
}
}
}

View file

@ -1,193 +0,0 @@
//---------------------------------------------------------------------
// <copyright file="EdmOpenApiDocumentGenerator.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData
{
/// <summary>
/// A class used to covert Edm model to <see cref="OpenApiDocument"/>
/// </summary>
internal class OpenApiDocumentGenerator
{
private OpenApiComponentsGenerator _componentsGenerator;
private OpenApiPathsGenerator _pathsGenerator;
/// <summary>
/// The Edm model.
/// </summary>
protected IEdmModel Model { get; }
/// <summary>
/// The Open Api document external configuration action.
/// </summary>
private Action<OpenApiDocument> _configure;
/// <summary>
///
/// </summary>
/// <param name="model"></param>
public OpenApiDocumentGenerator(IEdmModel model)
: this(model, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EdmOpenApiDocumentGenerator" /> class.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The Open Api writer settings.</param>
public OpenApiDocumentGenerator(IEdmModel model, Action<OpenApiDocument> configure)
{
Model = model ?? throw Error.ArgumentNull(nameof(model));
_configure = configure;
_componentsGenerator = new OpenApiComponentsGenerator(model);
_pathsGenerator = new OpenApiPathsGenerator(model);
}
/// <summary>
/// Generate Open Api document.
/// </summary>
/// <returns>The <see cref="OpenApiDocument"/> object.</returns>
public OpenApiDocument Generate()
{
OpenApiDocument document = new OpenApiDocument
{
SpecVersion = new Version(3, 0, 0),
Info = CreateInfo(),
Servers = CreateServers(),
Paths = CreatePaths(),
Components = CreateComponents(),
Tags = CreateTags()
};
_configure?.Invoke(document);
return document;
}
/// <summary>
/// Create <see cref="OpenApiInfo"/> object.
/// </summary>
/// <returns>The info object.</returns>
private OpenApiInfo CreateInfo()
{
// The value of info is an Info Object,
// It contains the fields title and version, and optionally the field description.
return new OpenApiInfo
{
// The value of title is the value of the unqualified annotation Core.Description
// of the main schema or the entity container of the OData service.
// If no Core.Description is present, a default title has to be provided as this is a required OpenAPI field.
Title = "OData Service for namespace " + Model.DeclaredNamespaces.FirstOrDefault(),
// The value of version is the value of the annotation Core.SchemaVersion(see[OData - VocCore]) of the main schema.
// If no Core.SchemaVersion is present, a default version has to be provided as this is a required OpenAPI field.
Version = "1.0.0",
// The value of description is the value of the annotation Core.LongDescription
// of the main schema or the entity container.
// While this field is optional, it prominently appears in OpenAPI exploration tools,
// so a default description should be provided if no Core.LongDescription annotation is present.
// Description = "This OData service is located at " + Settings.BaseUri?.OriginalString
};
}
/// <summary>
/// Create the collection of <see cref="OpenApiServer"/> object.
/// </summary>
/// <returns>The servers object.</returns>
private IList<OpenApiServer> CreateServers()
{
// The value of servers is an array of Server Objects.
// It contains one object with a field url.
// The value of url is a string cotaining the service root URL without the trailing forward slash.
return new List<OpenApiServer>
{
new OpenApiServer
{
Url = string.Empty
}
};
}
/// <summary>
/// Create the <see cref="OpenApiPaths"/> object.
/// </summary>
/// <returns>The paths object.</returns>
private OpenApiPaths CreatePaths()
{
return _pathsGenerator.Generate();
}
// <summary>
/// Create the <see cref="OpenApiComponents"/> object.
/// </summary>
/// <returns>The components object.</returns>
private OpenApiComponents CreateComponents()
{
return _componentsGenerator.Generate();
}
// <summary>
/// Create the collection of <see cref="OpenApiTag"/> object.
/// </summary>
/// <returns>The tag object.</returns>
private IList<OpenApiTag> CreateTags()
{
// The value of tags is an array of Tag Objects.
// For an OData service the natural groups are entity sets and singletons,
// so the tags array contains one Tag Object per entity set and singleton in the entity container.
IList<OpenApiTag> tags = new List<OpenApiTag>();
if (Model.EntityContainer != null)
{
foreach (IEdmEntityContainerElement element in Model.EntityContainer.Elements)
{
switch(element.ContainerElementKind)
{
case EdmContainerElementKind.EntitySet: // entity set
IEdmEntitySet entitySet = (IEdmEntitySet)element;
tags.Add(new OpenApiTag
{
Name = entitySet.Name,
Description = Model.GetDescription(entitySet)
});
break;
case EdmContainerElementKind.Singleton: // singleton
IEdmSingleton singleton = (IEdmSingleton)element;
tags.Add(new OpenApiTag
{
Name = singleton.Name,
Description = Model.GetDescription(singleton)
});
break;
// The tags array can contain additional Tag Objects for other logical groups,
// e.g. for action imports or function imports that are not associated with an entity set.
case EdmContainerElementKind.ActionImport: // Action Import
break;
case EdmContainerElementKind.FunctionImport: // Function Import
break;
}
}
}
// Tags is optional.
return tags.Any() ? tags : null;
}
}
}

View file

@ -1,159 +0,0 @@
//---------------------------------------------------------------------
// <copyright file="EdmEntitySetOpenApiElementGenerator.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData
{
internal class OpenApiPathItemGenerator
{
private IDictionary<IEdmTypeReference, IEdmOperation> _boundOperations;
private IEdmModel _model;
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiPathItemGenerator" /> class.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The Open Api writer settings.</param>
public OpenApiPathItemGenerator(IEdmModel model)
{
_model = model;
_boundOperations = new Dictionary<IEdmTypeReference, IEdmOperation>();
foreach (var edmOperation in model.SchemaElements.OfType<IEdmOperation>().Where(e => e.IsBound))
{
IEdmOperationParameter bindingParameter = edmOperation.Parameters.First();
_boundOperations.Add(bindingParameter.Type, edmOperation);
}
}
/// <summary>
/// Path items for Entity Sets.
/// Each entity set is represented as a name/value pair
/// whose name is the service-relative resource path of the entity set prepended with a forward slash,
/// and whose value is a Path Item Object, see [OpenAPI].
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="entitySet">The Edm entity set.</param>
/// <returns>The path items.</returns>
public IDictionary<string, OpenApiPathItem> CreatePaths(IEdmEntitySet entitySet)
{
if (entitySet == null)
{
return null;
}
IDictionary<string, OpenApiPathItem> paths = new Dictionary<string, OpenApiPathItem>();
// entity set
OpenApiPathItem pathItem = new OpenApiPathItem();
pathItem.AddOperation(OperationType.Get, entitySet.CreateGetOperationForEntitySet());
pathItem.AddOperation(OperationType.Post, entitySet.CreatePostOperationForEntitySet());
paths.Add("/" + entitySet.Name, pathItem);
// entity
string entityPathName = entitySet.CreatePathNameForEntity();
pathItem = new OpenApiPathItem();
pathItem.AddOperation(OperationType.Get, entitySet.CreateGetOperationForEntity());
pathItem.AddOperation(OperationType.Patch, entitySet.CreatePatchOperationForEntity());
pathItem.AddOperation(OperationType.Delete, entitySet.CreateDeleteOperationForEntity());
paths.Add(entityPathName, pathItem);
// bound operations
IDictionary<string, OpenApiPathItem> operations = CreatePathItemsWithOperations(entitySet);
foreach (var operation in operations)
{
paths.Add(operation);
}
return paths;
}
public IDictionary<string, OpenApiPathItem> CreatePaths(IEdmSingleton singleton)
{
IDictionary<string, OpenApiPathItem> paths = new Dictionary<string, OpenApiPathItem>();
// Singleton
string entityPathName = singleton.CreatePathNameForSingleton();
OpenApiPathItem pathItem = new OpenApiPathItem();
pathItem.AddOperation(OperationType.Get, singleton.CreateGetOperationForSingleton());
pathItem.AddOperation(OperationType.Patch, singleton.CreatePatchOperationForSingleton());
paths.Add(entityPathName, pathItem);
IDictionary<string, OpenApiPathItem> operations = CreatePathItemsWithOperations(singleton);
foreach (var operation in operations)
{
paths.Add(operation);
}
return paths;
}
private IDictionary<string, OpenApiPathItem> CreatePathItemsWithOperations(IEdmNavigationSource navigationSource)
{
IDictionary<string, OpenApiPathItem> operationPathItems = new Dictionary<string, OpenApiPathItem>();
IEnumerable<IEdmOperation> operations;
IEdmEntitySet entitySet = navigationSource as IEdmEntitySet;
// collection bound
if (entitySet != null)
{
operations = FindOperations(navigationSource.EntityType(), collection: true);
foreach (var operation in operations)
{
OpenApiPathItem openApiOperation = operation.CreatePathItem();
string operationPathName = operation.CreatePathItemName();
operationPathItems.Add("/" + navigationSource.Name + operationPathName, openApiOperation);
}
}
// non-collection bound
operations = FindOperations(navigationSource.EntityType(), collection: false);
foreach (var operation in operations)
{
OpenApiPathItem openApiOperation = operation.CreatePathItem();
string operationPathName = operation.CreatePathItemName();
string temp;
if (entitySet != null)
{
temp = entitySet.CreatePathNameForEntity();
}
else
{
temp = "/" + navigationSource.Name;
}
operationPathItems.Add(temp + operationPathName, openApiOperation);
}
return operationPathItems;
}
private IEnumerable<IEdmOperation> FindOperations(IEdmEntityType entityType, bool collection)
{
string fullTypeName = collection ? "Collection(" + entityType.FullName() + ")" :
entityType.FullName();
foreach (var item in _boundOperations)
{
if (item.Key.FullName() == fullTypeName)
{
yield return item.Value;
}
}
}
}
}

View file

@ -1,91 +0,0 @@
//---------------------------------------------------------------------
// <copyright file="EdmOpenApiPathsGenerator.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.OData
{
/// <summary>
/// Visit Edm model to generate <see cref="OpenApiPaths"/>
/// </summary>
internal class OpenApiPathsGenerator
{
private OpenApiPathItemGenerator _nsGenerator;
private IEdmModel _model;
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiPathsGenerator" /> class.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="settings">The Open Api writer settings.</param>
public OpenApiPathsGenerator(IEdmModel model)
{
_model = model;
_nsGenerator = new OpenApiPathItemGenerator(model);
}
/// <summary>
/// Create the <see cref="OpenApiPaths"/>
/// </summary>
/// <returns>the paths object.</returns>
public OpenApiPaths Generate()
{
OpenApiPaths paths = new OpenApiPaths();
if (_model.EntityContainer != null)
{
foreach (var element in _model.EntityContainer.Elements)
{
switch (element.ContainerElementKind)
{
case EdmContainerElementKind.EntitySet:
IEdmEntitySet entitySet = element as IEdmEntitySet;
if (entitySet != null)
{
foreach (var item in _nsGenerator.CreatePaths(entitySet))
{
paths.Add(item.Key, item.Value);
}
}
break;
case EdmContainerElementKind.Singleton:
IEdmSingleton singleton = element as IEdmSingleton;
if (singleton != null)
{
foreach (var item in _nsGenerator.CreatePaths(singleton))
{
paths.Add(item.Key, item.Value);
}
}
break;
case EdmContainerElementKind.FunctionImport:
IEdmFunctionImport functionImport = element as IEdmFunctionImport;
if (functionImport != null)
{
var functionImportPathItem = functionImport.CreatePathItem();
paths.Add(functionImport.CreatePathItemName(), functionImportPathItem);
}
break;
case EdmContainerElementKind.ActionImport:
IEdmActionImport actionImport = element as IEdmActionImport;
if (actionImport != null)
{
var functionImportPathItem = actionImport.CreatePathItem();
paths.Add(actionImport.CreatePathItemName(), functionImportPathItem);
}
break;
}
}
}
return paths;
}
}
}