Skip to content

Commit

Permalink
gRPC JSON transcoding: Fix body binding to repeated message or map (d…
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK authored Mar 31, 2023
1 parent e646901 commit 0706ea3
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,16 @@ public static async ValueTask<TRequest> ReadMessage<TRequest>(JsonTranscodingSer
// TODO: JsonSerializer currently doesn't support deserializing values onto an existing object or collection.
// Either update this to use new functionality in JsonSerializer or improve work-around perf.
type = JsonConverterHelper.GetFieldType(serverCallContext.DescriptorInfo.BodyFieldDescriptor);
type = type.GetGenericArguments()[0];
type = typeof(List<>).MakeGenericType(type);

var args = type.GetGenericArguments();
if (serverCallContext.DescriptorInfo.BodyFieldDescriptor.IsMap)
{
type = typeof(Dictionary<,>).MakeGenericType(args[0], args[1]);
}
else
{
type = typeof(List<>).MakeGenericType(args[0]);
}

GrpcServerLog.DeserializingMessage(serverCallContext.Logger, type);

Expand Down
23 changes: 21 additions & 2 deletions src/Grpc/JsonTranscoding/src/Shared/ServiceDescriptorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,22 @@ public static void RecursiveSetValue(IMessage currentValue, List<FieldDescriptor

public static void SetValue(IMessage message, FieldDescriptor field, object? values)
{
if (field.IsRepeated)
if (field.IsMap)
{
var map = (IDictionary)field.Accessor.GetValue(message);
if (values is IDictionary dictionaryValues)
{
foreach (DictionaryEntry value in dictionaryValues)
{
map[value.Key] = value.Value;
}
}
else
{
throw new InvalidOperationException("Map field requires repeating keys and values.");
}
}
else if (field.IsRepeated)
{
var list = (IList)field.Accessor.GetValue(message);
if (values is StringValues stringValues)
Expand All @@ -263,7 +278,11 @@ public static void SetValue(IMessage message, FieldDescriptor field, object? val
{
foreach (var value in listValues)
{
list.Add(ConvertValue(value, field));
var v = field.Accessor.Descriptor.FieldType == FieldType.Message
? value
: ConvertValue(value, field);

list.Add(v);
}
}
else
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ message HelloRequest {
string field_name = 22 [json_name="json_customized_name"];
google.protobuf.FloatValue float_value = 23;
string hiding_field_name = 24 [json_name="field_name"];
repeated SubMessage repeated_messages = 25;
map<int32, int32> map_keyint_valueint = 26;
}

message HelloReply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,15 +278,13 @@ public async Task HandleCallAsync_SubRepeatedBodySet_SetOnRequestMessage()
descriptorInfo);
var httpContext = TestHelpers.CreateHttpContext();

var sdf = new RepeatedField<string>
var sw = new StringWriter();
JsonFormatter.Default.WriteValue(sw, new RepeatedField<string>
{
"One",
"Two",
"Three"
};

var sw = new StringWriter();
JsonFormatter.Default.WriteValue(sw, sdf);
});

httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
Expand All @@ -310,6 +308,206 @@ public async Task HandleCallAsync_SubRepeatedBodySet_SetOnRequestMessage()
Assert.Equal("Three", request!.RepeatedStrings[2]);
}

[Fact]
public async Task HandleCallAsync_SubRepeatedMessageBodySet_SetOnRequestMessage()
{
// Arrange
HelloRequest? request = null;
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
{
request = r;
return Task.FromResult(new HelloReply { Message = $"Hello {r.Name}" });
};

var descriptorInfo = TestHelpers.CreateDescriptorInfo(
bodyDescriptor: HelloRequest.Types.SubMessage.Descriptor,
bodyDescriptorRepeated: true,
bodyFieldDescriptor: HelloRequest.Descriptor.FindFieldByName("repeated_messages"));
var unaryServerCallHandler = CreateCallHandler(
invoker,
descriptorInfo);
var httpContext = TestHelpers.CreateHttpContext();

var sw = new StringWriter();
JsonFormatter.Default.WriteValue(sw, new RepeatedField<HelloRequest.Types.SubMessage>
{
new HelloRequest.Types.SubMessage { Subfield = "One" },
new HelloRequest.Types.SubMessage { Subfield = "Two" },
new HelloRequest.Types.SubMessage { Subfield = "Three" }
});

httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
["name"] = "QueryStringTestName!",
["sub.subfield"] = "QueryStringTestSubfield!",
["sub.subfields"] = "QueryStringTestSubfields!"
});
httpContext.Request.ContentType = "application/json";

// Act
await unaryServerCallHandler.HandleCallAsync(httpContext);

// Assert
Assert.NotNull(request);
Assert.Equal("QueryStringTestName!", request!.Name);
Assert.Equal("QueryStringTestSubfield!", request!.Sub.Subfield);
Assert.Equal(3, request!.RepeatedMessages.Count);
Assert.Equal("One", request!.RepeatedMessages[0].Subfield);
Assert.Equal("Two", request!.RepeatedMessages[1].Subfield);
Assert.Equal("Three", request!.RepeatedMessages[2].Subfield);
}

[Fact]
public async Task HandleCallAsync_SubMapBodySet_SetOnRequestMessage()
{
// Arrange
HelloRequest? request = null;
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
{
request = r;
return Task.FromResult(new HelloReply { Message = $"Hello {r.Name}" });
};

var descriptorInfo = TestHelpers.CreateDescriptorInfo(
bodyDescriptor: HelloRequest.Types.SubMessage.Descriptor,
bodyDescriptorRepeated: true,
bodyFieldDescriptor: HelloRequest.Descriptor.FindFieldByName("map_strings"));
var unaryServerCallHandler = CreateCallHandler(
invoker,
descriptorInfo);
var httpContext = TestHelpers.CreateHttpContext();

var sw = new StringWriter();
JsonFormatter.Default.WriteValue(sw, new MapField<string, string>
{
["key1"] = "One",
["key2"] = "Two",
["key3"] = "Three"
});

httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
["name"] = "QueryStringTestName!",
["sub.subfield"] = "QueryStringTestSubfield!",
["sub.subfields"] = "QueryStringTestSubfields!"
});
httpContext.Request.ContentType = "application/json";

// Act
await unaryServerCallHandler.HandleCallAsync(httpContext);

// Assert
Assert.NotNull(request);
Assert.Equal("QueryStringTestName!", request!.Name);
Assert.Equal("QueryStringTestSubfield!", request!.Sub.Subfield);
Assert.Equal(3, request!.MapStrings.Count);
Assert.Equal("One", request!.MapStrings["key1"]);
Assert.Equal("Two", request!.MapStrings["key2"]);
Assert.Equal("Three", request!.MapStrings["key3"]);
}

[Fact]
public async Task HandleCallAsync_SubMapMessageBodySet_SetOnRequestMessage()
{
// Arrange
HelloRequest? request = null;
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
{
request = r;
return Task.FromResult(new HelloReply { Message = $"Hello {r.Name}" });
};

var descriptorInfo = TestHelpers.CreateDescriptorInfo(
bodyDescriptor: HelloRequest.Types.SubMessage.Descriptor,
bodyDescriptorRepeated: true,
bodyFieldDescriptor: HelloRequest.Descriptor.FindFieldByName("map_message"));
var unaryServerCallHandler = CreateCallHandler(
invoker,
descriptorInfo);
var httpContext = TestHelpers.CreateHttpContext();

var sw = new StringWriter();
JsonFormatter.Default.WriteValue(sw, new MapField<string, HelloRequest.Types.SubMessage>
{
["key1"] = new HelloRequest.Types.SubMessage { Subfield = "One" },
["key2"] = new HelloRequest.Types.SubMessage { Subfield = "Two" },
["key3"] = new HelloRequest.Types.SubMessage { Subfield = "Three" }
});

httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
["name"] = "QueryStringTestName!",
["sub.subfield"] = "QueryStringTestSubfield!",
["sub.subfields"] = "QueryStringTestSubfields!"
});
httpContext.Request.ContentType = "application/json";

// Act
await unaryServerCallHandler.HandleCallAsync(httpContext);

// Assert
Assert.NotNull(request);
Assert.Equal("QueryStringTestName!", request!.Name);
Assert.Equal("QueryStringTestSubfield!", request!.Sub.Subfield);
Assert.Equal(3, request!.MapMessage.Count);
Assert.Equal("One", request!.MapMessage["key1"].Subfield);
Assert.Equal("Two", request!.MapMessage["key2"].Subfield);
Assert.Equal("Three", request!.MapMessage["key3"].Subfield);
}

[Fact]
public async Task HandleCallAsync_SubMapIntBodySet_SetOnRequestMessage()
{
// Arrange
HelloRequest? request = null;
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
{
request = r;
return Task.FromResult(new HelloReply { Message = $"Hello {r.Name}" });
};

var descriptorInfo = TestHelpers.CreateDescriptorInfo(
bodyDescriptor: HelloRequest.Types.SubMessage.Descriptor,
bodyDescriptorRepeated: true,
bodyFieldDescriptor: HelloRequest.Descriptor.FindFieldByName("map_keyint_valueint"));
var unaryServerCallHandler = CreateCallHandler(
invoker,
descriptorInfo);
var httpContext = TestHelpers.CreateHttpContext();

var sw = new StringWriter();
JsonFormatter.Default.WriteValue(sw, new MapField<int, int>
{
[1] = 2,
[3] = 4,
[5] = 6
});

httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
["name"] = "QueryStringTestName!",
["sub.subfield"] = "QueryStringTestSubfield!",
["sub.subfields"] = "QueryStringTestSubfields!"
});
httpContext.Request.ContentType = "application/json";

// Act
await unaryServerCallHandler.HandleCallAsync(httpContext);

// Assert
Assert.NotNull(request);
Assert.Equal("QueryStringTestName!", request!.Name);
Assert.Equal("QueryStringTestSubfield!", request!.Sub.Subfield);
Assert.Equal(3, request!.MapKeyintValueint.Count);
Assert.Equal(2, request!.MapKeyintValueint[1]);
Assert.Equal(4, request!.MapKeyintValueint[3]);
Assert.Equal(6, request!.MapKeyintValueint[5]);
}

[Fact]
public async Task HandleCallAsync_MatchingQueryStringValues_SetOnRequestMessage()
{
Expand Down

0 comments on commit 0706ea3

Please sign in to comment.