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

Add Query Parameters Resolver to Grpc Swagger #545

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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 @@ -128,6 +128,19 @@ private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint,
ParameterDescriptor = parameterDescriptor!
});
}

foreach (var queryDescription in ServiceDescriptorHelpers.ResolveQueryParameterDescriptors(
routeParameters, methodDescriptor, bodyDescriptor?.Descriptor, bodyDescriptor?.FieldDescriptors))
{
apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
{
Name = queryDescription.Name,
ModelMetadata = new GrpcModelMetadata(
ModelMetadataIdentity.ForType(MessageDescriptorHelpers.ResolveFieldType(queryDescription.Field))),
Source = BindingSource.Query,
DefaultValue = string.Empty
});
}

return apiDescription;
}
Expand Down
59 changes: 59 additions & 0 deletions src/GrpcHttpApi/src/Shared/ServiceDescriptorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,65 @@ public static Dictionary<string, List<FieldDescriptor>> ResolveRouteParameterDes

return null;
}

public static IEnumerable<(string Name, FieldDescriptor Field)> ResolveQueryParameterDescriptors(
Dictionary<string, List<FieldDescriptor>> routeParameters,
MethodDescriptor methodDescriptor,
MessageDescriptor? bodyDescriptor,
List<FieldDescriptor>? bodyFieldDescriptors
)
{
if (methodDescriptor.InputType.Fields.InDeclarationOrder().Count <= routeParameters.Count)
{
yield break;
}

var allParameters = methodDescriptor.InputType.Fields.InDeclarationOrder().ToList();

var allParametersName = methodDescriptor.InputType.Fields
.InDeclarationOrder()
.Select(x => x.Name)
.ToList();

foreach (var pathParameter in routeParameters)
{
allParametersName.Remove(pathParameter.Key);
}

if (bodyDescriptor != null)
{
if (bodyFieldDescriptors != null)
{
// body with field name
foreach (var bodyFieldDescriptor in bodyFieldDescriptors)
{
allParametersName.Remove(bodyFieldDescriptor.Name);
}
}
else
{
// body with wildcard
foreach (var bodyFieldDescriptor in bodyDescriptor.Fields.InDeclarationOrder())
{
allParametersName.Remove(bodyFieldDescriptor.Name);
}
}
}

foreach (var parameterName in allParametersName)
{
var field = allParameters
.Where(x => x.Name == parameterName)
.Select(x => x)
.First();

allParameters.Remove(field);

(string Name, FieldDescriptor Field) queryDescription = (Name: parameterName, Field: field);

yield return queryDescription;
}
}

public record BodyDescriptorInfo(
MessageDescriptor Descriptor,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Linq;
using Count;
using Greet;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Grpc.Swagger.Tests.Services;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -86,6 +88,69 @@ public void AddGrpcSwagger_GrpcServiceWithGroupName_FilteredByGroup()
Assert.True(swagger.Paths["/v1/greeter/{name}"].Operations.ContainsKey(OperationType.Get));
Assert.True(swagger.Paths["/v1/add/{value1}/{value2}"].Operations.ContainsKey(OperationType.Get));
}

[Fact]
public void AddGrpcSwagger_GrpcServiceWithQuery_ResolveQueryParameterDescriptorsTest()
{
// Arrange & Act
var services = new ServiceCollection();
services.AddGrpcSwagger();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
services.AddRouting();
services.AddLogging();
services.AddSingleton<IWebHostEnvironment, TestWebHostEnvironment>();
var serviceProvider = services.BuildServiceProvider();
var app = new ApplicationBuilder(serviceProvider);

app.UseRouting();
app.UseEndpoints(c =>
{
c.MapGrpcService<ParametersService>();
});

var swaggerGenerator = serviceProvider.GetRequiredService<ISwaggerProvider>();
var swagger = swaggerGenerator.GetSwagger("v1");

// Base Assert
Assert.NotNull(swagger);

// Assert 1
var path = swagger.Paths["/v1/parameters1"];
Assert.True(path.Operations.ContainsKey(OperationType.Get));
Assert.True(path.Operations.First().Value.Parameters.Count == 2);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(0).In == ParameterLocation.Query);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(1).In == ParameterLocation.Query);

// Assert 2
path = swagger.Paths["/v1/parameters2/{parameter_int}"];
Assert.True(path.Operations.ContainsKey(OperationType.Get));
Assert.True(path.Operations.First().Value.Parameters.Count == 2);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(0).In == ParameterLocation.Path);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(1).In == ParameterLocation.Query);

// Assert 3
path = swagger.Paths["/v1/parameters3/{parameter_one}"];
Assert.True(path.Operations.ContainsKey(OperationType.Post));
Assert.True(path.Operations.First().Value.Parameters.Count == 3);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(0).In == ParameterLocation.Path);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(1).In == ParameterLocation.Query);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(2).In == ParameterLocation.Query);
// body with one parameter
Assert.NotNull(path.Operations.First().Value.RequestBody);
Assert.True(swagger.Components.Schemas["RequestBody"].Properties.Count == 1);

// Assert 4
path = swagger.Paths["/v1/parameters4/{parameter_two}"];
Assert.True(path.Operations.ContainsKey(OperationType.Post));
Assert.True(path.Operations.First().Value.Parameters.Count == 1);
Assert.True(path.Operations.First().Value.Parameters.ElementAt(0).In == ParameterLocation.Path);
// body with four parameters
Assert.NotNull(path.Operations.First().Value.RequestBody);
Assert.True(swagger.Components.Schemas["RequestTwo"].Properties.Count == 4);
}

private class TestWebHostEnvironment : IWebHostEnvironment
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Protobuf Include="Proto\xmldoc.proto" GrpcServices="Both" />
<Protobuf Include="Proto\greeter.proto" GrpcServices="Both" />
<Protobuf Include="Proto\messages.proto" GrpcServices="Both" />
<Protobuf Include="Proto\parameters.proto" GrpcServices="Both" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
syntax = "proto3";

package params;

import "google/api/annotations.proto";

// HttpRule: https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#google.api.HttpRule

service Parameters {
// parameter_int & parameter_string should be query parameters
rpc DemoParametersOne (RequestOne) returns (Response) {
option (google.api.http) = {
get: "/v1/parameters1"
};
}

// parameter_string should be query parameters
rpc DemoParametersTwo (RequestOne) returns (Response) {
option (google.api.http) = {
get: "/v1/parameters2/{parameter_int}"
};
}

// parameter_two & parameter_three should be query parameters
rpc DemoParametersThree (RequestTwo) returns (Response) {
option (google.api.http) = {
post: "/v1/parameters3/{parameter_one}"
body: "parameter_four"
};
}

// no query parameters
rpc DemoParametersFour (RequestTwo) returns (Response) {
option (google.api.http) = {
post: "/v1/parameters4/{parameter_two}"
body: "*"
};
}
}

message RequestOne {
int64 parameter_int = 1;
string parameter_string = 2;
}

message RequestTwo {

int64 parameter_one = 1;
string parameter_two = 2;
int64 parameter_three = 3;
RequestBody parameter_four = 45;
}

message RequestBody {
string request_body = 1 ;
}

message Response {
string message = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Threading.Tasks;
using Grpc.Core;
using Params;

namespace Microsoft.AspNetCore.Grpc.Swagger.Tests.Services;

public class ParametersService : Parameters.ParametersBase
{
public override Task<Response> DemoParametersOne(RequestOne requestId, ServerCallContext ctx)
{
return Task.FromResult(new Response {Message = "DemoParametersOne Response"});
}

public override Task<Response> DemoParametersTwo(RequestOne requestId, ServerCallContext ctx)
{
return Task.FromResult(new Response {Message = "DemoParametersTwo Response"});
}

public override Task<Response> DemoParametersThree(RequestTwo request, ServerCallContext ctx)
{
return Task.FromResult(new Response {Message = "DemoParametersThree Response "});
}

public override Task<Response> DemoParametersFour(RequestTwo request, ServerCallContext ctx)
{
return Task.FromResult(new Response {Message = "DemoParametersFour Response"});
}
}