Skip to content
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

makes collection responses reusable #157

Merged
merged 16 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from 15 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
5 changes: 5 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,10 @@ internal static class Constants
/// Name used for the OpenAPI referenced schema for OData Count operations responses.
/// </summary>
public static string DollarCountSchemaName = "ODataCountResponse";

/// <summary>
/// Suffix used for collection response schemas.
/// </summary>
public static string CollectionSchemaSuffix = "CollectionResponse";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,25 @@ public static IDictionary<string, OpenApiResponse> CreateResponses(this ODataCon
{
Utils.CheckArgumentNull(context, nameof(context));

return new Dictionary<string, OpenApiResponse>
var responses = new Dictionary<string, OpenApiResponse>
{
{ "error", CreateErrorResponse() }
};

if(context.Settings.EnableDollarCountPath)
responses[Constants.DollarCountSchemaName] = CreateCountResponse();
baywet marked this conversation as resolved.
Show resolved Hide resolved

responses = responses.Concat(context.GetAllCollectionEntityTypes()
.Select(x => new KeyValuePair<string, OpenApiResponse>(
$"{(x is IEdmEntityType eType ? eType.FullName() : x.FullTypeName())}{Constants.CollectionSchemaSuffix}",
CreateCollectionResponse(x)))
.Where(x => !responses.ContainsKey(x.Key)))
.ToDictionary(x => x.Key, x => x.Value);

if(context.HasAnyNonContainedCollections())
responses[$"String{Constants.CollectionSchemaSuffix}"] = CreateCollectionResponse("String");

return responses;
}

/// <summary>
Expand Down Expand Up @@ -165,6 +180,61 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp
return responses;
}

private static OpenApiResponse CreateCollectionResponse(IEdmStructuredType structuredType)
{
var entityType = structuredType as IEdmEntityType;
return CreateCollectionResponse(entityType?.FullName() ?? structuredType.FullTypeName());
}
private static OpenApiResponse CreateCollectionResponse(string typeName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be remove this private static method and put the codes directly into the above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No because this method is called twice

{
return new OpenApiResponse
{
Description = "Retrieved collection",
Content = new Dictionary<string, OpenApiMediaType>
{
{
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = $"{typeName}{Constants.CollectionSchemaSuffix}"
}
}
}
}
}
};
}

private static OpenApiResponse CreateCountResponse()
{
OpenApiSchema schema = new()
{
Reference = new() {
Type = ReferenceType.Schema,
Id = Constants.DollarCountSchemaName
}
};
return new OpenApiResponse
{
Description = "The count of the resource",
Content = new Dictionary<string, OpenApiMediaType>
{
{
"text/plain",
new OpenApiMediaType
{
Schema = schema
}
}
}
};
}

private static OpenApiResponse CreateErrorResponse()
{
return new OpenApiResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,100 @@ public static IDictionary<string, OpenApiSchema> CreateSchemas(this ODataContext
Format = "int32"
};

schemas = schemas.Concat(context.GetAllCollectionEntityTypes()
.Select(x => new KeyValuePair<string, OpenApiSchema>(
$"{(x is IEdmEntityType eType ? eType.FullName() : x.FullTypeName())}{Constants.CollectionSchemaSuffix}",
CreateCollectionSchema(context, x)))
.Where(x => !schemas.ContainsKey(x.Key)))
.ToDictionary(x => x.Key, x => x.Value);

if(context.HasAnyNonContainedCollections())
schemas[$"String{Constants.CollectionSchemaSuffix}"] = CreateCollectionSchema(context, new OpenApiSchema { Type = "string" }, "string");

return schemas;
}
internal static bool HasAnyNonContainedCollections(this ODataContext context)
{
return context.Model
.SchemaElements
.OfType<IEdmStructuredType>()
.SelectMany(x => x.NavigationProperties())
.Any(x => x.TargetMultiplicity() == EdmMultiplicity.Many && !x.ContainsTarget);
}
internal static IEnumerable<IEdmStructuredType> GetAllCollectionEntityTypes(this ODataContext context)
{
var collectionEntityTypes = new HashSet<IEdmStructuredType>(
(context.EntityContainer?
.EntitySets()
.Select(x => x.EntityType()) ??
Enumerable.Empty<IEdmStructuredType>())
.Union(context.Model
.SchemaElements
.OfType<IEdmStructuredType>()
.SelectMany(x => x.NavigationProperties())
.Where(x => x.TargetMultiplicity() == EdmMultiplicity.Many)
.Select(x => x.Type.ToStructuredType()))
.Distinct()); // we could include actions and functions but actions are not pageable by nature (OData.NextLink) and functions might have specific annotations (deltalink)
var derivedCollectionTypes = collectionEntityTypes.SelectMany(x => context.Model.FindAllDerivedTypes(x).OfType<IEdmStructuredType>())
.Where(x => !collectionEntityTypes.Contains(x))
.Distinct()
.ToArray();
return collectionEntityTypes.Union(derivedCollectionTypes);
}

private static OpenApiSchema CreateCollectionSchema(ODataContext context, IEdmStructuredType structuredType)
{
OpenApiSchema schema = null;
var entityType = structuredType as IEdmEntityType;

if (context.Settings.EnableDerivedTypesReferencesForResponses && entityType != null)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(entityType, context.Model);
}

if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = entityType?.FullName() ?? structuredType.FullTypeName()
}
};
}
return CreateCollectionSchema(context, schema, entityType?.Name ?? structuredType.FullTypeName());
}
private static OpenApiSchema CreateCollectionSchema(ODataContext context, OpenApiSchema schema, string typeName)
{
var properties = new Dictionary<string, OpenApiSchema>
{
{
"value",
new OpenApiSchema
{
Type = "array",
Items = schema
}
}
};
if (context.Settings.EnablePagination)
{
properties.Add(
"@odata.nextLink",
new OpenApiSchema
{
Type = "string"
});
}

return new OpenApiSchema
{
Title = $"Collection of {typeName}",
Type = "object",
Properties = properties
};
}

/// <summary>
/// Create a <see cref="OpenApiSchema"/> for a <see cref="IEdmEnumType"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------

using System.Collections.Generic;
using System.Linq;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Common;
Expand Down Expand Up @@ -55,30 +54,15 @@ protected override void SetBasicInfo(OpenApiOperation operation)
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = new()
{
Reference = new() {
Type = ReferenceType.Schema,
Id = Constants.DollarCountSchemaName
}
};

operation.Responses = new OpenApiResponses
{
{
Constants.StatusCode200,
new OpenApiResponse
{
Description = "The count of the resource",
Content = new Dictionary<string, OpenApiMediaType>
{
{
"text/plain",
new OpenApiMediaType
{
Schema = schema
}
}
Reference = new OpenApiReference() {
Type = ReferenceType.Response,
Id = Constants.DollarCountSchemaName
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,69 +130,17 @@ protected override void SetParameters(OpenApiOperation operation)
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
OpenApiSchema schema = null;

if (Context.Settings.EnableDerivedTypesReferencesForResponses)
{
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
}

if (schema == null)
{
schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = EntitySet.EntityType().FullName()
}
};
}

var properties = new Dictionary<string, OpenApiSchema>
{
{
"value",
new OpenApiSchema
{
Type = "array",
Items = schema
}
}
};

if (Context.Settings.EnablePagination)
{
properties.Add(
"@odata.nextLink",
new OpenApiSchema
{
Type = "string"
});
}

operation.Responses = new OpenApiResponses
{
{
Constants.StatusCode200,
new OpenApiResponse
{
Description = "Retrieved entities",
Content = new Dictionary<string, OpenApiMediaType>
Reference = new OpenApiReference()
{
{
Constants.ApplicationJsonMediaType,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Title = "Collection of " + EntitySet.EntityType().Name,
Type = "object",
Properties = properties
}
}
}
}
Type = ReferenceType.Response,
Id = $"{EntitySet.EntityType().FullName()}{Constants.CollectionSchemaSuffix}"
},
}
}
};
Expand Down
Loading