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

Eliminate duplicated request bodies and responses for actions/functions #266

Merged
merged 8 commits into from
Sep 8, 2022
Merged
23 changes: 22 additions & 1 deletion src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,28 @@ public static bool IsOperationOverload(this IEdmModel model, IEdmOperation opera
}

/// <summary>
/// Check whether the operaiton import is overload in the model.
/// Checks whether operation targets singleton and entityset of the same type.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="operation"></param>
millicentachieng marked this conversation as resolved.
Show resolved Hide resolved
/// <returns>The test operations.</returns>
public static bool OperationTargetsMultiplePaths(this IEdmModel model, IEdmOperation operation)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(operation, nameof(operation));

if (!operation.Parameters.Any())
return false;

IEdmTypeReference bindingParameterType = operation.Parameters.First().Type;

return model.EntityContainer.EntitySets().Select(x => x.EntityType())
.Concat(model.EntityContainer.Singletons().Select(x => x.EntityType()))
.Where(x => x.FullName == bindingParameterType.FullName).Count() > 1;
millicentachieng marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Check whether the operation import is overload in the model.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="operationImport">The test operations.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,26 @@ public static IDictionary<string, OpenApiRequestBody> CreateRequestBodies(this O
{
Utils.CheckArgumentNull(context, nameof(context));

return new Dictionary<string, OpenApiRequestBody>
Dictionary<string, OpenApiRequestBody> requestBodies = new()
{
{
Constants.ReferencePostRequestBodyName,
CreateRefPostRequestBody()
CreateRefPostRequestBody()
},
{
Constants.ReferencePutRequestBodyName,
CreateRefPutRequestBody()
}
};

// add request bodies for actions targeting multiple related paths
foreach (IEdmAction action in context.Model.SchemaElements.OfType<IEdmAction>()
.Where(action => context.Model.OperationTargetsMultiplePaths(action)))
{
requestBodies.Add($"{action.Name}RequestBody", context.CreateRequestBody(action));
}

return requestBodies;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ public static IDictionary<string, OpenApiResponse> CreateResponses(this ODataCon
if(context.HasAnyNonContainedCollections())
responses[$"String{Constants.CollectionSchemaSuffix}"] = CreateCollectionResponse("String");

foreach (IEdmOperation operation in context.Model.SchemaElements.OfType<IEdmOperation>()
.Where(op => context.Model.OperationTargetsMultiplePaths(op)))
{
responses[$"{operation.Name}Response"] = context.CreateOperationResponse(operation);
}

return responses;
}

Expand All @@ -115,86 +121,115 @@ public static IDictionary<string, OpenApiResponse> CreateResponses(this ODataCon
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="operationImport">The Edm operation import.</param>
/// <param name="path">The OData path.</param>
/// <returns>The created <see cref="OpenApiResponses"/>.</returns>
public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperationImport operationImport, ODataPath path)
public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperationImport operationImport)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(operationImport, nameof(operationImport));
Utils.CheckArgumentNull(path, nameof(path));

return context.CreateResponses(operationImport.Operation, path);
return context.CreateResponses(operationImport.Operation);
}

/// <summary>
/// Create the <see cref="OpenApiResponses"/> for a <see cref="IEdmOperation"/>
/// </summary>
/// <param name="context">The OData context.</param>
/// <param name="operation">The Edm operation.</param>
/// <param name="path">The OData path.</param>
/// <returns>The created <see cref="OpenApiResponses"/>.</returns>
public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperation operation, ODataPath path)
public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOperation operation)
{
Utils.CheckArgumentNull(context, nameof(context));
Utils.CheckArgumentNull(operation, nameof(operation));
Utils.CheckArgumentNull(path, nameof(path));

OpenApiResponses responses = new();

if (operation.IsAction() && operation.ReturnType == null)
{
responses.Add(Constants.StatusCode204, Constants.StatusCode204.GetResponse());
}
else if (context.Model.OperationTargetsMultiplePaths(operation) )
{
responses.Add(
context.Settings.UseSuccessStatusCodeRange ? Constants.StatusCodeClass2XX : Constants.StatusCode200,
new OpenApiResponse
{
UnresolvedReference = true,
Reference = new OpenApiReference()
{
Type = ReferenceType.Response,
Id = $"{operation.Name}Response"
}
}
);
}
else
{
OpenApiSchema schema;
if (operation.ReturnType.IsCollection())
OpenApiResponse response = context.CreateOperationResponse(operation);
responses.Add(context.Settings.UseSuccessStatusCodeRange ? Constants.StatusCodeClass2XX : Constants.StatusCode200, response);
}

if (context.Settings.ErrorResponsesAsDefault)
{
responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse());
}
else
{
responses.Add(Constants.StatusCodeClass4XX, Constants.StatusCodeClass4XX.GetResponse());
responses.Add(Constants.StatusCodeClass5XX, Constants.StatusCodeClass5XX.GetResponse());
}

return responses;
}

public static OpenApiResponse CreateOperationResponse(this ODataContext context, IEdmOperation operation)
{
OpenApiSchema schema;
if (operation.ReturnType.IsCollection())
{
schema = new OpenApiSchema
{
// Get the entity type of the previous segment
IEdmEntityType entityType = path.Segments.Reverse().Skip(1)?.Take(1)?.FirstOrDefault()?.EntityType;
schema = new OpenApiSchema
{
Title = entityType == null ? null : $"Collection of {entityType.Name}",
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
Title = operation.ReturnType.Definition.AsElementType() is not IEdmEntityType entityType
? null : $"Collection of {entityType.Name}",
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
millicentachieng marked this conversation as resolved.
Show resolved Hide resolved
{
"value", context.CreateEdmTypeSchema(operation.ReturnType)
}
}
};
}
else if (operation.ReturnType.IsPrimitive())
};
}
else if (operation.ReturnType.IsPrimitive())
{
// A property or operation response that is of a primitive type is represented as an object with a single name/value pair,
// whose name is value and whose value is a primitive value.
schema = new OpenApiSchema
{
// A property or operation response that is of a primitive type is represented as an object with a single name/value pair,
// whose name is value and whose value is a primitive value.
schema = new OpenApiSchema
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
millicentachieng marked this conversation as resolved.
Show resolved Hide resolved
{
"value", context.CreateEdmTypeSchema(operation.ReturnType)
}
}
};
}
else
{
schema = context.CreateEdmTypeSchema(operation.ReturnType);
}
};
}
else
{
schema = context.CreateEdmTypeSchema(operation.ReturnType);
}

string mediaType = Constants.ApplicationJsonMediaType;
if (operation.ReturnType.AsPrimitive()?.PrimitiveKind() == EdmPrimitiveTypeKind.Stream)
{
// Responses of types Edm.Stream should be application/octet-stream
mediaType = Constants.ApplicationOctetStreamMediaType;
}
string mediaType = Constants.ApplicationJsonMediaType;
if (operation.ReturnType.AsPrimitive()?.PrimitiveKind() == EdmPrimitiveTypeKind.Stream)
{
// Responses of types Edm.Stream should be application/octet-stream
mediaType = Constants.ApplicationOctetStreamMediaType;
}

OpenApiResponse response = new()
{
Description = "Success",
Content = new Dictionary<string, OpenApiMediaType>
OpenApiResponse response = new()
{
Description = "Success",
Content = new Dictionary<string, OpenApiMediaType>
{
millicentachieng marked this conversation as resolved.
Show resolved Hide resolved
{
mediaType,
Expand All @@ -204,21 +239,9 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp
}
}
}
};
responses.Add(context.Settings.UseSuccessStatusCodeRange ? Constants.StatusCodeClass2XX : Constants.StatusCode200, response);
}

if (context.Settings.ErrorResponsesAsDefault)
{
responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse());
}
else
{
responses.Add(Constants.StatusCodeClass4XX, Constants.StatusCodeClass4XX.GetResponse());
responses.Add(Constants.StatusCodeClass5XX, Constants.StatusCodeClass5XX.GetResponse());
}
};

return responses;
return response;
}

private static OpenApiResponse CreateCollectionResponse(IEdmStructuredType structuredType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,24 @@ protected override void SetBasicInfo(OpenApiOperation operation)
/// <inheritdoc/>
protected override void SetRequestBody(OpenApiOperation operation)
{
IEdmAction action = EdmOperation as IEdmAction;
if (action != null)
if (EdmOperation is IEdmAction action)
millicentachieng marked this conversation as resolved.
Show resolved Hide resolved
{
operation.RequestBody = Context.CreateRequestBody(action);
if (Context.Model.OperationTargetsMultiplePaths(action))
{
operation.RequestBody = new OpenApiRequestBody
{
UnresolvedReference = true,
Reference = new OpenApiReference
{
Type = ReferenceType.RequestBody,
Id = $"{action.Name}RequestBody"
}
};
}
else
{
operation.RequestBody = Context.CreateRequestBody(action);
}
}

base.SetRequestBody(operation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ protected override void SetResponses(OpenApiOperation operation)
// describing the structure of the success response by referencing an appropriate schema
// in the global schemas. In addition, it contains a default name/value pair for
// the OData error response referencing the global responses.
operation.Responses = Context.CreateResponses(EdmOperationImport, Path);
operation.Responses = Context.CreateResponses(EdmOperationImport);

base.SetResponses(operation);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ protected override void SetParameters(OpenApiOperation operation)
/// <inheritdoc/>
protected override void SetResponses(OpenApiOperation operation)
{
operation.Responses = Context.CreateResponses(EdmOperation, Path);
operation.Responses = Context.CreateResponses(EdmOperation);
base.SetResponses(operation);
}

Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Microsoft.OpenApi.OData.Vocabulary.Core.LinkRelKey.List = 1 -> Microsoft.OpenApi
Microsoft.OpenApi.OData.Vocabulary.Core.LinkRelKey.ReadByKey = 0 -> Microsoft.OpenApi.OData.Vocabulary.Core.LinkRelKey
Microsoft.OpenApi.OData.Vocabulary.Core.LinkRelKey.Update = 3 -> Microsoft.OpenApi.OData.Vocabulary.Core.LinkRelKey
static Microsoft.OpenApi.OData.Common.OpenApiOperationExtensions.AddErrorResponses(this Microsoft.OpenApi.Models.OpenApiOperation operation, Microsoft.OpenApi.OData.OpenApiConvertSettings settings, bool addNoContent = false, Microsoft.OpenApi.Models.OpenApiSchema schema = null) -> void
static Microsoft.OpenApi.OData.Edm.EdmModelExtensions.OperationTargetsMultiplePaths(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmOperation operation) -> bool
static Microsoft.OpenApi.OData.Edm.EdmTypeExtensions.ShouldPathParameterBeQuoted(this Microsoft.OData.Edm.IEdmType edmType, Microsoft.OpenApi.OData.OpenApiConvertSettings settings) -> bool
Microsoft.OpenApi.OData.Edm.IODataPathProvider
Microsoft.OpenApi.OData.Edm.IODataPathProvider.CanFilter(Microsoft.OData.Edm.IEdmElement element) -> bool
Expand Down
Loading