From ed891e82ff0fc1c1bfb4b61aa92d70b0805cd0b9 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 13 Oct 2025 13:48:48 -0700 Subject: [PATCH] [SignalR] Add parameter index to invocation binding exception message --- .../test/FunctionalTests/HubConnectionTests.cs | 2 +- .../com/microsoft/signalr/GsonHubProtocol.java | 16 ++++++++++++++-- .../microsoft/signalr/GsonHubProtocolTest.java | 10 +++++++--- .../src/Protocol/JsonHubProtocol.cs | 2 +- .../src/Protocol/MessagePackHubProtocolWorker.cs | 5 +++-- .../src/Protocol/NewtonsoftJsonHubProtocol.cs | 7 ++++--- .../Protocol/JsonHubProtocolTestsBase.cs | 10 +++++----- .../Protocol/MessagePackHubProtocolTestBase.cs | 4 ++-- 8 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index d14d9cc4ce7b..760574c964f5 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -1244,7 +1244,7 @@ public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentTypeMismat var channel = await connection.StreamAsChannelAsync("Stream", "xyz"); var ex = await Assert.ThrowsAsync(() => channel.ReadAndCollectAllAsync().DefaultTimeout()); - Assert.Equal("Failed to invoke 'Stream' due to an error on the server. InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex.Message); + Assert.Equal("Failed to invoke 'Stream' due to an error on the server. InvalidDataException: Error binding argument 1. Make sure that the types of the provided values match the types of the hub method being invoked.", ex.Message); } catch (Exception ex) { diff --git a/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java b/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java index 026f5da7e0a2..1dc0c0720622 100644 --- a/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/GsonHubProtocol.java @@ -237,7 +237,13 @@ private ArrayList bindArguments(JsonArray argumentsToken, List par if (paramTypes.size() >= 1) { arguments = new ArrayList<>(); for (int i = 0; i < paramTypes.size(); i++) { - arguments.add(gson.fromJson(argumentsToken.get(i), paramTypes.get(i))); + Object obj; + try { + obj = gson.fromJson(argumentsToken.get(i), paramTypes.get(i)); + } catch (Exception ex) { + throw new RuntimeException(String.format("Error binding argument %d. Make sure that the types of the provided values match the types of the method being invoked.", i + 1), ex); + } + arguments.add(obj); } } @@ -251,7 +257,13 @@ private ArrayList bindArguments(JsonReader reader, List paramTypes ArrayList arguments = new ArrayList<>(); while (reader.peek() != JsonToken.END_ARRAY) { if (argCount < paramCount) { - arguments.add(gson.fromJson(reader, paramTypes.get(argCount))); + Object obj; + try { + obj = gson.fromJson(reader, paramTypes.get(argCount)); + } catch (Exception ex) { + throw new RuntimeException(String.format("Error binding argument %d. Make sure that the types of the provided values match the types of the method being invoked.", argCount + 1), ex); + } + arguments.add(obj); } else { reader.skipValue(); } diff --git a/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java index 2ecfd483c7f5..0e52753cafa2 100644 --- a/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/test/src/main/java/com/microsoft/signalr/GsonHubProtocolTest.java @@ -399,7 +399,8 @@ public void invocationBindingFailureWhenParsingIncorrectType() { assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("java.lang.NumberFormatException: For input string: \"true\"", invocationBindingFailureMessage.getException().getMessage()); + assertEquals("Error binding argument 1. Make sure that the types of the provided values match the types of the method being invoked.", invocationBindingFailureMessage.getException().getMessage()); + assertEquals("java.lang.NumberFormatException: For input string: \"true\"", invocationBindingFailureMessage.getException().getCause().getMessage()); } @Test @@ -415,7 +416,8 @@ public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("java.lang.NumberFormatException: For input string: \"true\"", invocationBindingFailureMessage.getException().getMessage()); + assertEquals("Error binding argument 1. Make sure that the types of the provided values match the types of the method being invoked.", invocationBindingFailureMessage.getException().getMessage()); + assertEquals("java.lang.NumberFormatException: For input string: \"true\"", invocationBindingFailureMessage.getException().getCause().getMessage()); assertEquals("123", invocationBindingFailureMessage.getInvocationId()); } @@ -444,7 +446,9 @@ public void invocationBindingFailureWhenParsingLocalDateTimeWithoutAppropriateTy assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, message.getMessageType()); InvocationBindingFailureMessage failureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("com.google.gson.JsonSyntaxException", failureMessage.getException().getClass().getName()); + assertEquals("Error binding argument 1. Make sure that the types of the provided values match the types of the method being invoked.", failureMessage.getException().getMessage()); + // Inner exception is inconsistent across versions/implementations, so we don't assert specifics + assertNotNull(failureMessage.getException().getCause()); } @Test diff --git a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs index 35fdbf90f1a3..365ae50c1bdd 100644 --- a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs +++ b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs @@ -850,7 +850,7 @@ private static HubMessage BindInvocationMessage(string? invocationId, string tar } catch (Exception ex) { - throw new InvalidDataException("Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex); + throw new InvalidDataException($"Error binding argument {paramIndex + 1}. Make sure that the types of the provided values match the types of the hub method being invoked.", ex); } } else diff --git a/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocolWorker.cs b/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocolWorker.cs index ba0490bb2055..047afc19f7f5 100644 --- a/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocolWorker.cs +++ b/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocolWorker.cs @@ -303,10 +303,11 @@ private static SequenceMessage CreateSequenceMessage(ref MessagePackReader reade $"Invocation provides {argumentCount} argument(s) but target expects {parameterTypes.Count}."); } + var i = 0; try { var arguments = new object?[argumentCount]; - for (var i = 0; i < argumentCount; i++) + for (; i < argumentCount; i++) { arguments[i] = DeserializeObject(ref reader, parameterTypes[i], "argument"); } @@ -315,7 +316,7 @@ private static SequenceMessage CreateSequenceMessage(ref MessagePackReader reade } catch (Exception ex) { - throw new InvalidDataException("Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex); + throw new InvalidDataException($"Error binding argument {i + 1}. Make sure that the types of the provided values match the types of the hub method being invoked.", ex); } } diff --git a/src/SignalR/common/Protocols.NewtonsoftJson/src/Protocol/NewtonsoftJsonHubProtocol.cs b/src/SignalR/common/Protocols.NewtonsoftJson/src/Protocol/NewtonsoftJsonHubProtocol.cs index 671658a2c415..3c68d5bf145f 100644 --- a/src/SignalR/common/Protocols.NewtonsoftJson/src/Protocol/NewtonsoftJsonHubProtocol.cs +++ b/src/SignalR/common/Protocols.NewtonsoftJson/src/Protocol/NewtonsoftJsonHubProtocol.cs @@ -854,7 +854,7 @@ private static bool ReadArgumentAsType(JsonTextReader reader, IReadOnlyList t.Name); public static IEnumerable ArgumentBindingErrorNames => ArgumentBindingErrors.Keys.Select(name => new object[] { name });