diff --git a/JsonRpc.Standard/Contracts/JsonRpcMethod.cs b/JsonRpc.Standard/Contracts/JsonRpcMethod.cs index fbed72f..605a87d 100644 --- a/JsonRpc.Standard/Contracts/JsonRpcMethod.cs +++ b/JsonRpc.Standard/Contracts/JsonRpcMethod.cs @@ -92,7 +92,9 @@ internal object[] UnmarshalArguments(MarshaledRequest request) continue; } // Resolve other parameters, considering the optional - var jarg = request.Message.Parameters?[this.Parameters[i].ParameterName]; + var jarg = request.Message.Parameters.Type == JTokenType.Object + ? request.Message.Parameters?[this.Parameters[i].ParameterName] + : request.Message.Parameters?[i]; if (jarg == null) { if (this.Parameters[i].IsOptional) diff --git a/JsonRpc.Standard/Contracts/RpcMethodBinder.cs b/JsonRpc.Standard/Contracts/RpcMethodBinder.cs index 1956b18..7489440 100644 --- a/JsonRpc.Standard/Contracts/RpcMethodBinder.cs +++ b/JsonRpc.Standard/Contracts/RpcMethodBinder.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using JsonRpc.Standard.Server; @@ -31,15 +33,42 @@ internal class JsonRpcMethodBinder : IJsonRpcMethodBinder /// public JsonRpcMethod TryBindToMethod(ICollection candidates, RequestContext context) { - //TODO Support array as params // context.Request.Parameters can be: {}, [], null (JValue), null - var paramsObj = context.Request.Parameters as JObject; - if (context.Request.Parameters is JArray) return null; + // Parameters MAY be omitted. + if (context.Request.Parameters == null || context.Request.Parameters.Type == JTokenType.Null) + return TryBindToParameterlessMethod(candidates); + // by-name + if (context.Request.Parameters is JObject paramsObj) + return TryBindToMethod(candidates, paramsObj); + // by-position + if (context.Request.Parameters is JArray paramsArray) + return TryBindToMethod(candidates, paramsArray); + // Other invalid cases, e.g., naked JValue. + return null; + } + + private JsonRpcMethod TryBindToParameterlessMethod(ICollection candidates) + { + JsonRpcMethod firstMatch = null; + foreach (var m in candidates) + { + if (m.Parameters.Count == 0 || m.Parameters.All(p => p.IsOptional)) + { + if (firstMatch != null) throw new AmbiguousMatchException(); + firstMatch = m; + } + } + return firstMatch; + } + + private JsonRpcMethod TryBindToMethod(ICollection candidates, JObject paramsObj) + { + Debug.Assert(paramsObj != null); JsonRpcMethod firstMatch = null; Dictionary requestProp = null; foreach (var m in candidates) { - if (!m.AllowExtensionData && paramsObj != null) + if (!m.AllowExtensionData) { // Strict match requestProp = paramsObj.Properties().ToDictionary(p => p.Name, p => p.Value); @@ -64,5 +93,31 @@ public JsonRpcMethod TryBindToMethod(ICollection candidates, Requ } return firstMatch; } + + private JsonRpcMethod TryBindToMethod(ICollection candidates, JArray paramsArray) + { + Debug.Assert(paramsArray != null); + JsonRpcMethod firstMatch = null; + foreach (var m in candidates) + { + if (!m.AllowExtensionData && paramsArray.Count > m.Parameters.Count) goto NEXT; + for (var i = 0; i < m.Parameters.Count; i++) + { + var param = m.Parameters[i]; + var jparam = i < paramsArray.Count ? paramsArray[i] : null; + if (jparam == null) + { + if (!param.IsOptional) goto NEXT; + else continue; + } + if (!param.MatchJTokenType(jparam.Type)) goto NEXT; + } + if (firstMatch != null) throw new AmbiguousMatchException(); + firstMatch = m; + NEXT: + ; + } + return firstMatch; + } } } diff --git a/JsonRpc.Standard/Server/JsonRpcServiceHost.cs b/JsonRpc.Standard/Server/JsonRpcServiceHost.cs index 83aeba5..e19a597 100644 --- a/JsonRpc.Standard/Server/JsonRpcServiceHost.cs +++ b/JsonRpc.Standard/Server/JsonRpcServiceHost.cs @@ -127,12 +127,6 @@ private async Task DispatchRpcMethod(RequestContext context) { args = method.UnmarshalArguments(new MarshaledRequest(context.Request, context.CancellationToken)); } - catch (ArgumentException ex) - { - // Signature not match. This is not likely to happen. Still there might be problem with binder. - TrySetErrorResponse(context, JsonRpcErrorCode.InvalidParams, ex.Message); - return; - } catch (Exception ex) { TrySetErrorResponse(context, JsonRpcErrorCode.InvalidParams, ex.Message); diff --git a/UnitTestProject1/ServiceHostTests.cs b/UnitTestProject1/ServiceHostTests.cs index 14049aa..db139b3 100644 --- a/UnitTestProject1/ServiceHostTests.cs +++ b/UnitTestProject1/ServiceHostTests.cs @@ -17,8 +17,7 @@ public class ServiceHostTests : UnitTestBase public ServiceHostTests(ITestOutputHelper output) : base(output) { } - - + [Fact] public async Task BasicServiceHostTest() { @@ -50,5 +49,26 @@ public async Task BasicServiceHostTest() Assert.Equal(JsonRpcErrorCode.UnhandledClrException, (JsonRpcErrorCode) response.Error.Code); } + [Fact] + public async Task InvokeWithPositionalArgumentsTest() + { + var host = Utility.CreateJsonRpcServiceHost(this); + var response = await host.InvokeAsync( + new RequestMessage(123, "add", JToken.FromObject(new[] {20, 35})), + null, + CancellationToken.None); + Assert.NotNull(response); + Assert.Equal(123, response.Id); + Assert.Null(response.Error); + Assert.Equal(55, (int)response.Result); + response = await host.InvokeAsync( + new RequestMessage("TEST", "add", JToken.FromObject(new[] {"abc", "def"})), + null, + CancellationToken.None); + Assert.NotNull(response); + Assert.Equal("TEST", response.Id); + Assert.Null(response.Error); + Assert.Equal("abcdef", (string)response.Result); + } } }