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
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,56 @@ public static IList<OpenApiParameter> CreateKeyParameters(this ODataContext cont
return parameters;
}

/// <summary>
/// Creates the path parameters for the <see cref="ODataPath"/>
/// </summary>
/// <param name="path">The ODataPath</param>
/// <param name="context">The OData context.</param>
/// <returns>The created list of <see cref="OpenApiParameter"/></returns>
public static List<OpenApiParameter> CreatePathParameters(this ODataPath path, ODataContext context)
{
List<OpenApiParameter> pathParameters = new();
var parameterMappings = path.CalculateParameterMapping(context.Settings);

foreach (ODataKeySegment keySegment in path.OfType<ODataKeySegment>())
{
IDictionary<string, string> mapping = parameterMappings[keySegment];
pathParameters.AddRange(context.CreateKeyParameters(keySegment, mapping));
}

// Add the route prefix parameter v1{data}
if (context.Settings.RoutePathPrefixProvider?.Parameters != null)
{
pathParameters.AddRange(context.Settings.RoutePathPrefixProvider.Parameters);
}

return pathParameters;
}

/// <summary>
/// Adds an OpenApiParameter to an existing list of OpenApiParameters.
/// If a parameter with the same name already exists in the list, the new parameter name
/// if suffixed with an incrementing number
/// </summary>
/// <param name="parameters">The list of OpenApiParameters to be appended to</param>
/// <param name="parameter">The new OpenApiParameter to be appended</param>
public static void AppendParameter(this IList<OpenApiParameter> parameters, OpenApiParameter parameter)
{
HashSet<string> parametersSet = new(parameters.Select(p => p.Name));

string parameterName = parameter.Name;
int index = 1;
while (parametersSet.Contains(parameterName))
{
parameterName += index.ToString();
index++;
}

parameter.Name = parameterName;
parametersSet.Add(parameterName);
parameters.Add(parameter);
}

/// <summary>
/// Create the $top parameter.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ public string PathPrefix
/// </summary>
public string InnerErrorComplexTypeName { get; set; } = "InnerError";

/// <summary>
/// Gets/Sets a value indicating whether path parameters should be declared on path item object.
/// If true, path parameters will be declared on the path item object, otherwise they
/// will be declared on the operation object.
/// </summary>
public bool DeclarePathParametersOnPathItem { get; set; } = false;

/// <summary>
/// Gets/Sets a value indicating whether or not to use restrictions annotations to generate paths for complex properties.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected override void SetParameters(OpenApiOperation operation)
{
foreach (var param in Context.CreateParameters(functionImport.Function, OperationImportSegment.ParameterMappings))
{
AppendParameter(operation, param);
operation.Parameters.AppendParameter(param);
}
}
else
Expand All @@ -39,7 +39,7 @@ protected override void SetParameters(OpenApiOperation operation)
// and it contains specific Parameter Objects for the allowed system query options.
foreach (var param in Context.CreateParameters(functionImport))
{
AppendParameter(operation, param);
operation.Parameters.AppendParameter(param);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ protected override void SetParameters(OpenApiOperation operation)
IList<OpenApiParameter> parameters = Context.CreateParameters(function, OperationSegment.ParameterMappings);
foreach (var parameter in parameters)
{
AppendParameter(operation, parameter);
operation.Parameters.AppendParameter(parameter);
}
}
else
Expand All @@ -153,7 +153,7 @@ protected override void SetParameters(OpenApiOperation operation)
{
foreach (var parameter in parameters)
{
AppendParameter(operation, parameter);
operation.Parameters.AppendParameter(parameter);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ protected override void SetResponses(OpenApiOperation operation)
if (Context.Settings.ShowLinks)
{
links = Context.CreateLinks(entityType: EntitySet.EntityType(), entityName: EntitySet.Name,
entityKind: EntitySet.ContainerElementKind.ToString(), parameters: operation.Parameters);
entityKind: EntitySet.ContainerElementKind.ToString(), PathParameters);
}

if (schema == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ protected override void SetResponses(OpenApiOperation operation)
string operationId = GetOperationId();

links = Context.CreateLinks(entityType: NavigationProperty.ToEntityType(), entityName: NavigationProperty.Name,
entityKind: NavigationProperty.PropertyKind.ToString(), parameters: operation.Parameters,
entityKind: NavigationProperty.PropertyKind.ToString(), parameters: PathParameters,
navPropOperationId: operationId);
}

Expand Down
48 changes: 10 additions & 38 deletions src/Microsoft.OpenApi.OData.Reader/Operation/OperationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ internal abstract class OperationHandler : IOperationHandler

protected IDictionary<ODataSegment, IDictionary<string, string>> ParameterMappings;

/// <summary>
/// The path parameters in the path
/// </summary>
protected IList<OpenApiParameter> PathParameters;

/// <inheritdoc/>
public virtual OpenApiOperation CreateOperation(ODataContext context, ODataPath path)
{
Expand Down Expand Up @@ -139,25 +144,16 @@ protected virtual void SetRequestBody(OpenApiOperation operation)
/// <param name="operation">The <see cref="OpenApiOperation"/>.</param>
protected virtual void SetParameters(OpenApiOperation operation)
{
foreach (ODataKeySegment keySegment in Path.OfType<ODataKeySegment>())
PathParameters = Path.CreatePathParameters(Context);
if (!Context.Settings.DeclarePathParametersOnPathItem)
{
IDictionary<string, string> mapping = ParameterMappings[keySegment];
foreach (var p in Context.CreateKeyParameters(keySegment, mapping))
foreach (var parameter in PathParameters)
{
AppendParameter(operation, p);
operation.Parameters.AppendParameter(parameter);
}
}

AppendCustomParameters(operation);

// Add the route prefix parameter v1{data}
if (Context.Settings.RoutePathPrefixProvider != null && Context.Settings.RoutePathPrefixProvider.Parameters != null)
{
foreach (var parameter in Context.Settings.RoutePathPrefixProvider.Parameters)
{
operation.Parameters.Add(parameter);
}
}
}

/// <summary>
Expand Down Expand Up @@ -247,32 +243,8 @@ protected static void AppendCustomParameters(OpenApiOperation operation, IList<C
}
}

AppendParameter(operation, parameter);
}
}

protected static void AppendParameter(OpenApiOperation operation, OpenApiParameter parameter)
{
HashSet<string> set = new HashSet<string>(operation.Parameters.Select(p => p.Name));

if (!set.Contains(parameter.Name))
{
operation.Parameters.Add(parameter);
return;
operation.Parameters.AppendParameter(parameter);
}

int index = 1;
string originalName = parameter.Name;
string newName;
do
{
newName = originalName + index.ToString();
index++;
}
while (set.Contains(newName));

parameter.Name = newName;
operation.Parameters.Add(parameter);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ protected override void SetResponses(OpenApiOperation operation)
string operationId = GetOperationId();

links = Context.CreateLinks(entityType: NavigationProperty.ToEntityType(), entityName: NavigationProperty.Name,
entityKind: NavigationProperty.PropertyKind.ToString(), parameters: operation.Parameters,
entityKind: NavigationProperty.PropertyKind.ToString(), parameters: PathParameters,
navPropOperationId: operationId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected override void SetResponses(OpenApiOperation operation)
if (Context.Settings.ShowLinks)
{
links = Context.CreateLinks(entityType: Singleton.EntityType(), entityName: Singleton.Name,
entityKind: Singleton.ContainerElementKind.ToString(), parameters: operation.Parameters);
entityKind: Singleton.ContainerElementKind.ToString(), parameters: PathParameters);
}

if (schema == null)
Expand Down
18 changes: 18 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/PathItem/PathItemHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Common;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Generator;
using Microsoft.OpenApi.OData.Operation;
using Microsoft.OpenApi.OData.Properties;

Expand Down Expand Up @@ -45,6 +46,11 @@ public virtual OpenApiPathItem CreatePathItem(ODataContext context, ODataPath pa

SetBasicInfo(item);

if (Context.Settings.DeclarePathParametersOnPathItem)
{
SetParameters(item);
}

SetOperations(item);

SetExtensions(item);
Expand Down Expand Up @@ -101,5 +107,17 @@ protected virtual void AddOperation(OpenApiPathItem item, OperationType operatio
IOperationHandler operationHander = provider.GetHandler(Path.Kind, operationType);
item.AddOperation(operationType, operationHander.CreateOperation(Context, Path));
}

/// <summary>
/// Set the parameters information for <see cref="OpenApiPathItem"/>
/// </summary>
/// <param name="item">The <see cref="OpenApiPathItem"/>.</param>
protected virtual void SetParameters(OpenApiPathItem item)
{
foreach (var parameter in Path.CreatePathParameters(Context))
{
item.Parameters.AppendParameter(parameter);
}
}
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ Microsoft.OpenApi.OData.OpenApiConvertSettings.ErrorResponsesAsDefault.set -> vo
Microsoft.OpenApi.OData.OpenApiConvertSettings.ErrorResponsesAsDefault.get -> bool
Microsoft.OpenApi.OData.OpenApiConvertSettings.InnerErrorComplexTypeName.set -> void
Microsoft.OpenApi.OData.OpenApiConvertSettings.InnerErrorComplexTypeName.get -> string
Microsoft.OpenApi.OData.OpenApiConvertSettings.DeclarePathParametersOnPathItem.get -> bool
Microsoft.OpenApi.OData.OpenApiConvertSettings.DeclarePathParametersOnPathItem.set -> void
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.GetPathItemName(Microsoft.OpenApi.OData.OpenApiConvertSettings settings, System.Collections.Generic.HashSet<string> parameters) -> string
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.Identifier.get -> string
override Microsoft.OpenApi.OData.Edm.ODataDollarCountSegment.EntityType.get -> Microsoft.OData.Edm.IEdmEntityType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,45 @@ public void CreateEntityPathItemReturnsCorrectPathItem()
pathItem.Operations.Select(o => o.Key));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void CreateEntityPathItemReturnsCorrectPathItemWithPathParameters(bool declarePathParametersOnPathItem)
{
// Arrange
IEdmModel model = EntitySetPathItemHandlerTests.GetEdmModel(annotation: "");
OpenApiConvertSettings convertSettings = new OpenApiConvertSettings
{
DeclarePathParametersOnPathItem = declarePathParametersOnPathItem,
};
ODataContext context = new ODataContext(model, convertSettings);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType()));

// Act
var pathItem = _pathItemHandler.CreatePathItem(context, path);

// Assert
Assert.NotNull(pathItem);

Assert.NotNull(pathItem.Operations);
Assert.NotEmpty(pathItem.Operations);
Assert.Equal(3, pathItem.Operations.Count);
Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Patch, OperationType.Delete },
pathItem.Operations.Select(o => o.Key));

if (declarePathParametersOnPathItem)
{
Assert.NotEmpty(pathItem.Parameters);
Assert.Equal(1, pathItem.Parameters.Count);
}
else
{
Assert.Empty(pathItem.Parameters);
}
}

[Fact]
public void CreateEntityPathItemReturnsCorrectPathItemWithReferences()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,65 @@ public void CreateCollectionNavigationPropertyPathItemReturnsCorrectPathItem(boo
Assert.NotEmpty(pathItem.Description);
}

[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void CreateNavigationPropertyPathItemReturnsCorrectPathItemWithPathParameters(bool keySegment, bool declarePathParametersOnPathItem)
{
// Arrange
IEdmModel model = GetEdmModel("");
OpenApiConvertSettings settings = new()
{
DeclarePathParametersOnPathItem = declarePathParametersOnPathItem,
};
ODataContext context = new ODataContext(model, settings);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
IEdmEntityType entityType = entitySet.EntityType();

IEdmNavigationProperty property = entityType.DeclaredNavigationProperties()
.FirstOrDefault(c => c.ContainsTarget == true && c.TargetMultiplicity() == EdmMultiplicity.Many);
Assert.NotNull(property);

ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet),
new ODataKeySegment(entityType),
new ODataNavigationPropertySegment(property));

if (keySegment)
{
path.Push(new ODataKeySegment(property.ToEntityType()));
}

// Act
var pathItem = _pathItemHandler.CreatePathItem(context, path);

// Assert
Assert.NotNull(pathItem);

Assert.NotNull(pathItem.Operations);
Assert.NotEmpty(pathItem.Operations);
Assert.NotEmpty(pathItem.Description);

if (declarePathParametersOnPathItem)
{
Assert.NotEmpty(pathItem.Parameters);
if (keySegment)
{
Assert.Equal(2, pathItem.Parameters.Count); // Customer ID and ContainedOrderLines Order ID
}
else
{
Assert.Equal(1, pathItem.Parameters.Count); // Customer ID
}
}
else
{
Assert.Empty(pathItem.Parameters);
}
}

[Theory]
[InlineData(true, new OperationType[] { OperationType.Get, OperationType.Patch, OperationType.Delete })]
[InlineData(false, new OperationType[] { OperationType.Get })]
Expand Down