Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 singletons and/or entitysets of the same type.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="operation">The test operations.</param>
/// <returns>True/false.</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(static x => x.EntityType())
.Concat(model.EntityContainer.Singletons().Select(static x => x.EntityType()))
.Where(x => x.FullName().Equals(bindingParameterType.FullName(), StringComparison.OrdinalIgnoreCase)).Count() > 1;
}

/// <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,28 @@ 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)))
{
OpenApiRequestBody requestBody = context.CreateRequestBody(action);
if (requestBody != null)
requestBodies.Add($"{action.Name}RequestBody", requestBody);
}

return requestBodies;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ 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)))
{
OpenApiResponse response = context.CreateOperationResponse(operation);
if (response != null)
responses[$"{operation.Name}Response"] = response;
}

return responses;
}

Expand All @@ -115,110 +123,130 @@ 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
else if (context.Model.OperationTargetsMultiplePaths(operation))
{
OpenApiSchema schema;
if (operation.ReturnType.IsCollection())
{
// Get the entity type of the previous segment
IEdmEntityType entityType = path.Segments.Reverse().Skip(1)?.Take(1)?.FirstOrDefault()?.EntityType;
schema = new OpenApiSchema
responses.Add(
context.Settings.UseSuccessStatusCodeRange ? Constants.StatusCodeClass2XX : Constants.StatusCode200,
new OpenApiResponse
{
Title = entityType == null ? null : $"Collection of {entityType.Name}",
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
UnresolvedReference = true,
Reference = new OpenApiReference()
{
{
"value", context.CreateEdmTypeSchema(operation.ReturnType)
}
Type = ReferenceType.Response,
Id = $"{operation.Name}Response"
}
};
}
else if (operation.ReturnType.IsPrimitive())
}
);
}
else
{
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)
{
if (operation.ReturnType == null)
return null;

OpenApiSchema schema;
if (operation.ReturnType.IsCollection())
{
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
Title = operation.ReturnType.Definition.AsElementType() is not IEdmEntityType entityType
? null : $"Collection of {entityType.Name}",
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
Comment thread
millicentachieng marked this conversation as resolved.
Resolved
{
"value", context.CreateEdmTypeSchema(operation.ReturnType)
}
"value", 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;
}

OpenApiResponse response = new()
}
};
}
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
{
Description = "Success",
Content = new Dictionary<string, OpenApiMediaType>
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
{
Comment thread
millicentachieng marked this conversation as resolved.
Resolved
mediaType,
new OpenApiMediaType
{
Schema = schema
}
"value", context.CreateEdmTypeSchema(operation.ReturnType)
}
}
};
responses.Add(context.Settings.UseSuccessStatusCodeRange ? Constants.StatusCodeClass2XX : Constants.StatusCode200, response);
}

if (context.Settings.ErrorResponsesAsDefault)
else
{
responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse());
schema = context.CreateEdmTypeSchema(operation.ReturnType);
}
else

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

return responses;
OpenApiResponse response = new()
{
Description = "Success",
Content = new Dictionary<string, OpenApiMediaType>
{
{
Comment thread
millicentachieng marked this conversation as resolved.
Resolved
mediaType,
new OpenApiMediaType
{
Schema = schema
}
}
}
};

return response;
}

private static OpenApiResponse CreateCollectionResponse(IEdmStructuredType structuredType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<RepositoryUrl>https://github.com/Microsoft/OpenAPI.NET.OData</RepositoryUrl>
<PackageReleaseNotes>
- Fixes response schemas of actions and functions that return a collection to contain the nextLink property #231
- Fixes duplicated actions/functions request body/response schemas #241
</PackageReleaseNotes>
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
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)
{
operation.RequestBody = Context.CreateRequestBody(action);
if (EdmOperation is IEdmAction action && Context.CreateRequestBody(action) is OpenApiRequestBody requestBody)
{
if (Context.Model.OperationTargetsMultiplePaths(action))
{
operation.RequestBody = new OpenApiRequestBody
{
UnresolvedReference = true,
Reference = new OpenApiReference
{
Type = ReferenceType.RequestBody,
Id = $"{action.Name}RequestBody"
}
};
}
else
{
operation.RequestBody = requestBody;
}
}

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