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

Report argument deserialization failures more precisely #402

Merged
merged 2 commits into from
Jan 3, 2020

Conversation

AArnott
Copy link
Member

@AArnott AArnott commented Jan 2, 2020

Instead of responding with a general exception that claims the client didn't send an argument for a given parameter, we now report that an argument deserialization failed, and include all inner exception details.

BEFORE

Unable to find method 'SynchronizeTextAsync/3' on {no object} for the following reasons: An argument was not supplied for a required parameter.

AFTER

Unable to find method 'MethodWithArgThatFailsToDeserialize/1' on {no object} for the following reasons: Deserializing JSON-RPC argument with name "arg1" and position 0 to type "JsonRpcTests+TypeThrowsWhenDeserialized" failed: This exception is meant to be thrown.

In addition to the improved message, the RemoteMethodNotFoundException type now contains extra data with all the type, message and callstack info for all inner exceptions similar to how it would if the exception had been thrown from the RPC server method itself. The full JSON-RPC error message sent to the client and accessible through this exception is:

{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32602,
    "message": "Unable to find method 'MethodWithArgThatFailsToDeserialize/1' on {no object} for the following reasons: Deserializing JSON-RPC argument with name \"arg1\" and position 0 to type \"JsonRpcTests+TypeThrowsWhenDeserialized\" failed: This exception is meant to be thrown.",
    "data": {
      "type": "System.AggregateException",
      "message": "One or more errors occurred.",
      "stack": null,
      "code": -2146233088,
      "inner": {
        "type": "StreamJsonRpc.RpcArgumentDeserializationException",
        "message": "Deserializing JSON-RPC argument with name \"arg1\" and position 0 to type \"JsonRpcTests+TypeThrowsWhenDeserialized\" failed: This exception is meant to be thrown.",
        "stack": "   at StreamJsonRpc.JsonMessageFormatter.JsonRpcRequest.TryGetArgumentByNameOrIndex(String name, Int32 position, Type typeHint, Object& value) in D:\\git\\streamjsonrpc\\src\\StreamJsonRpc\\JsonMessageFormatter.cs:line 748\r\n   at StreamJsonRpc.Protocol.JsonRpcRequest.TryGetTypedArguments(ReadOnlySpan`1 parameters, Span`1 typedArguments) in D:\\git\\streamjsonrpc\\src\\StreamJsonRpc\\Protocol\\JsonRpcRequest.cs:line 166\r\n   at StreamJsonRpc.JsonMessageFormatter.JsonRpcRequest.TryGetTypedArguments(ReadOnlySpan`1 parameters, Span`1 typedArguments) in D:\\git\\streamjsonrpc\\src\\StreamJsonRpc\\JsonMessageFormatter.cs:line 717\r\n   at StreamJsonRpc.TargetMethod.TryGetArguments(JsonRpcRequest request, MethodSignature method, Span`1 arguments) in D:\\git\\streamjsonrpc\\src\\StreamJsonRpc\\Reflection\\TargetMethod.cs:line 156\r\n   at StreamJsonRpc.TargetMethod..ctor(JsonRpcRequest request, List`1 candidateMethodTargets) in D:\\git\\streamjsonrpc\\src\\StreamJsonRpc\\Reflection\\TargetMethod.cs:line 48",
        "code": -2146233088,
        "inner": {
          "type": "System.Exception",
          "message": "This exception is meant to be thrown.",
          "stack": "   at JsonRpcJsonHeadersTests.TypeThrowsWhenDeserializedConverter.ReadJson(JsonReader reader, Type objectType, TypeThrowsWhenDeserialized existingValue, Boolean hasExistingValue, JsonSerializer serializer) in D:\\git\\streamjsonrpc\\src\\StreamJsonRpc.Tests\\JsonRpcJsonHeadersTests.cs:line 243\r\n   at Newtonsoft.Json.JsonConverter`1.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)\r\n   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)\r\n   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)\r\n   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)\r\n   at Newtonsoft.Json.Linq.JToken.ToObject(Type objectType, JsonSerializer jsonSerializer)\r\n   at StreamJsonRpc.JsonMessageFormatter.JsonRpcRequest.TryGetArgumentByNameOrIndex(String name, Int32 position, Type typeHint, Object& value) in D:\\git\\streamjsonrpc\\src\\StreamJsonRpc\\JsonMessageFormatter.cs:line 732",
          "code": -2146233088,
          "inner": null
        }
      }
    }
  }
}

When there are multiple overloads and all of them fail, an exception for each overload that failed will be included so the investigation can consider why each overload was a mismatch.

When one overload fails but another overload succeeds, the succeeding overload is invoked as before. But even in that case, we now trace a warning message regarding the conversion failure as it occurs.

Fixes #400

@AArnott AArnott added this to the v2.3 milestone Jan 2, 2020
@AArnott AArnott self-assigned this Jan 2, 2020
@codecov-io
Copy link

codecov-io commented Jan 2, 2020

Codecov Report

Merging #402 into master will increase coverage by 0.44%.
The diff coverage is 98.14%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #402      +/-   ##
==========================================
+ Coverage   90.14%   90.59%   +0.44%     
==========================================
  Files          47       48       +1     
  Lines        3624     3668      +44     
==========================================
+ Hits         3267     3323      +56     
+ Misses        357      345      -12
Impacted Files Coverage Δ
src/StreamJsonRpc/Protocol/JsonRpcRequest.cs 87.23% <ø> (ø) ⬆️
src/StreamJsonRpc/JsonRpc.cs 93.05% <100%> (+0.12%) ⬆️
src/StreamJsonRpc/MessagePackFormatter.cs 93.79% <100%> (+0.34%) ⬆️
src/StreamJsonRpc/Reflection/TargetMethod.cs 94.87% <100%> (+0.66%) ⬆️
.../Exceptions/RpcArgumentDeserializationException.cs 100% <100%> (ø)
...eamJsonRpc/Exceptions/RemoteInvocationException.cs 42.85% <100%> (+21.8%) ⬆️
src/StreamJsonRpc/JsonMessageFormatter.cs 92.85% <100%> (+0.03%) ⬆️
src/StreamJsonRpc/Resources.Designer.cs 67.14% <100%> (+0.47%) ⬆️
...sonRpc/Exceptions/RemoteMethodNotFoundException.cs 52.94% <83.33%> (+16.57%) ⬆️
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f1565ae...6b15e9c. Read the comment docs.

Instead of responding with a general exception that claims the client didn't send an argument for a given parameter, we now report that an argument deserialization failed, and include all inner exception details.

Fixes microsoft#400
@sharwell
Copy link
Member

sharwell commented Jan 3, 2020

yessssssssssss

@AArnott AArnott merged commit 4cda1e2 into microsoft:master Jan 3, 2020
@AArnott AArnott deleted the fix400 branch January 3, 2020 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JsonRpc wraps failure to deserialize argument with misleading exception
4 participants