Skip to content

Commit

Permalink
.Net: fix: includes path item path parameters to OpenAPI document par…
Browse files Browse the repository at this point in the history
…sing (#9969)

fixes #9962

---------

Signed-off-by: Vincent Biret <[email protected]>
Co-authored-by: SergeyMenshykh <[email protected]>
  • Loading branch information
baywet and SergeyMenshykh authored Dec 16, 2024
1 parent 6d02eef commit 5874188
Show file tree
Hide file tree
Showing 4 changed files with 525 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ internal static List<RestApiOperation> CreateRestApiOperations(OpenApiDocument d
path: path,
method: new HttpMethod(method),
description: string.IsNullOrEmpty(operationItem.Description) ? operationItem.Summary : operationItem.Description,
parameters: CreateRestApiOperationParameters(operationItem.OperationId, operationItem.Parameters),
parameters: CreateRestApiOperationParameters(operationItem.OperationId, operationItem.Parameters.Union(pathItem.Parameters, s_parameterNameAndLocationComparer)),
payload: CreateRestApiOperationPayload(operationItem.OperationId, operationItem.RequestBody),
responses: CreateRestApiOperationExpectedResponses(operationItem.Responses).ToDictionary(static item => item.Item1, static item => item.Item2),
securityRequirements: CreateRestApiOperationSecurityRequirements(operationItem.Security)
Expand All @@ -237,6 +237,27 @@ internal static List<RestApiOperation> CreateRestApiOperations(OpenApiDocument d
}
}

private static readonly ParameterNameAndLocationComparer s_parameterNameAndLocationComparer = new();

/// <summary>
/// Compares two <see cref="OpenApiParameter"/> objects by their name and location.
/// </summary>
private sealed class ParameterNameAndLocationComparer : IEqualityComparer<OpenApiParameter>
{
public bool Equals(OpenApiParameter? x, OpenApiParameter? y)
{
if (x is null || y is null)
{
return x == y;
}
return this.GetHashCode(x) == this.GetHashCode(y);
}
public int GetHashCode([DisallowNull] OpenApiParameter obj)
{
return HashCode.Combine(obj.Name, obj.In);
}
}

/// <summary>
/// Build a list of <see cref="RestApiServer"/> objects from the given list of <see cref="OpenApiServer"/> objects.
/// </summary>
Expand Down Expand Up @@ -381,7 +402,7 @@ internal static List<RestApiSecurityRequirement> CreateRestApiOperationSecurityR
/// <param name="operationId">The operation id.</param>
/// <param name="parameters">The OpenAPI parameters.</param>
/// <returns>The parameters.</returns>
private static List<RestApiParameter> CreateRestApiOperationParameters(string operationId, IList<OpenApiParameter> parameters)
private static List<RestApiParameter> CreateRestApiOperationParameters(string operationId, IEnumerable<OpenApiParameter> parameters)
{
var result = new List<RestApiParameter>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
Expand Down Expand Up @@ -434,6 +435,165 @@ public async Task ItCanFilterOutSpecifiedOperationsAsync()
Assert.Contains(restApiSpec.Operations, o => o.Id == "SetSecret");
Assert.Contains(restApiSpec.Operations, o => o.Id == "GetSecret");
}
[Fact]
public async Task ItCanParsePathItemPathParametersAsync()
{
var document =
"""
{
"swagger": "2.0",
"info": {
"title": "Test API",
"version": "1.0.0"
},
"paths": {
"/items/{itemId}/{format}": {
"parameters": [
{
"name": "itemId",
"in": "path",
"required": true,
"type": "string"
}
],
"get": {
"parameters": [
{
"name": "format",
"in": "path",
"required": true,
"type": "string"
}
],
"summary": "Get an item by ID",
"responses": {
"200": {
"description": "Successful response"
}
}
}
}
}
}
""";

await using var steam = new MemoryStream(Encoding.UTF8.GetBytes(document));
var restApi = await this._sut.ParseAsync(steam);

Assert.NotNull(restApi);
Assert.NotNull(restApi.Operations);
Assert.NotEmpty(restApi.Operations);

var firstOperation = restApi.Operations[0];

Assert.NotNull(firstOperation);
Assert.Equal("Get an item by ID", firstOperation.Description);
Assert.Equal("/items/{itemId}/{format}", firstOperation.Path);

var parameters = firstOperation.GetParameters();
Assert.NotNull(parameters);
Assert.Equal(2, parameters.Count);

var pathParameter = parameters.Single(static p => "itemId".Equals(p.Name, StringComparison.OrdinalIgnoreCase));
Assert.NotNull(pathParameter);
Assert.True(pathParameter.IsRequired);
Assert.Equal(RestApiParameterLocation.Path, pathParameter.Location);
Assert.Null(pathParameter.DefaultValue);
Assert.NotNull(pathParameter.Schema);
Assert.Equal("string", pathParameter.Schema.RootElement.GetProperty("type").GetString());

var formatParameter = parameters.Single(static p => "format".Equals(p.Name, StringComparison.OrdinalIgnoreCase));
Assert.NotNull(formatParameter);
Assert.True(formatParameter.IsRequired);
Assert.Equal(RestApiParameterLocation.Path, formatParameter.Location);
Assert.Null(formatParameter.DefaultValue);
Assert.NotNull(formatParameter.Schema);
Assert.Equal("string", formatParameter.Schema.RootElement.GetProperty("type").GetString());
}

[Fact]
public async Task ItCanParsePathItemPathParametersAndOverridesAsync()
{
var document =
"""
{
"swagger": "2.0",
"info": {
"title": "Test API",
"version": "1.0.0"
},
"paths": {
"/items/{itemId}/{format}": {
"parameters": [
{
"name": "itemId",
"in": "path",
"required": true,
"type": "string"
}
],
"get": {
"parameters": [
{
"name": "format",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "itemId",
"in": "path",
"description": "item ID override",
"required": true,
"type": "string"
}
],
"summary": "Get an item by ID",
"responses": {
"200": {
"description": "Successful response"
}
}
}
}
}
}
""";

await using var steam = new MemoryStream(Encoding.UTF8.GetBytes(document));
var restApi = await this._sut.ParseAsync(steam);

Assert.NotNull(restApi);
Assert.NotNull(restApi.Operations);
Assert.NotEmpty(restApi.Operations);

var firstOperation = restApi.Operations[0];

Assert.NotNull(firstOperation);
Assert.Equal("Get an item by ID", firstOperation.Description);
Assert.Equal("/items/{itemId}/{format}", firstOperation.Path);

var parameters = firstOperation.GetParameters();
Assert.NotNull(parameters);
Assert.Equal(2, parameters.Count);

var pathParameter = parameters.Single(static p => "itemId".Equals(p.Name, StringComparison.OrdinalIgnoreCase));
Assert.NotNull(pathParameter);
Assert.True(pathParameter.IsRequired);
Assert.Equal(RestApiParameterLocation.Path, pathParameter.Location);
Assert.Null(pathParameter.DefaultValue);
Assert.NotNull(pathParameter.Schema);
Assert.Equal("string", pathParameter.Schema.RootElement.GetProperty("type").GetString());
Assert.Equal("item ID override", pathParameter.Description);

var formatParameter = parameters.Single(static p => "format".Equals(p.Name, StringComparison.OrdinalIgnoreCase));
Assert.NotNull(formatParameter);
Assert.True(formatParameter.IsRequired);
Assert.Equal(RestApiParameterLocation.Path, formatParameter.Location);
Assert.Null(formatParameter.DefaultValue);
Assert.NotNull(formatParameter.Schema);
Assert.Equal("string", formatParameter.Schema.RootElement.GetProperty("type").GetString());
}

private static RestApiParameter GetParameterMetadata(IList<RestApiOperation> operations, string operationId,
RestApiParameterLocation location, string name)
Expand Down
Loading

0 comments on commit 5874188

Please sign in to comment.