Skip to content

Commit

Permalink
Merge pull request #236 from nenoNaninu/receiver_CancellationToken
Browse files Browse the repository at this point in the history
Support `CancellationToken` in the last parameter of a receive method
  • Loading branch information
nenoNaninu authored Sep 25, 2024
2 parents f740233 + d5b09c6 commit 75f539a
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 30 deletions.
18 changes: 14 additions & 4 deletions src/TypedSignalR.Client.TypeScript.Analyzer/InterfaceAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public class InterfaceAnalyzer : DiagnosticAnalyzer
};

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(AnnotationRule, HubAttributeAnnotationRule, ReceiverAttributeAnnotationRule, UnsupportedTypeRule, HubMethodReturnTypeRule, ReceiverMethodReturnTypeRule);
=> ImmutableArray.Create(AnnotationRule, HubAttributeAnnotationRule, ReceiverAttributeAnnotationRule, UnsupportedTypeRule, HubMethodReturnTypeRule, ReceiverMethodReturnTypeRule);

public override void Initialize(AnalysisContext context)
{
Expand Down Expand Up @@ -224,13 +224,23 @@ private static void AnalyzeReceiverInterface(
// Return type must be Task or Task<T>
ValidateReceiverReturnType(context, method, supportTypeSymbols, transpilationSourceAttributeSymbol, specialSymbols);

foreach (var parameter in method.Parameters)
// Validate method parameters type
for (int i = 0; i < method.Parameters.Length; i++)
{
ValidateType(context, parameter.Type, parameter.Locations[0], supportTypeSymbols, transpilationSourceAttributeSymbol);
if (IsLast(i, method.Parameters.Length)
// It is allowed to pass a CancellationToken as the last parameter of the receiver's method.
&& SymbolEqualityComparer.Default.Equals(method.Parameters[i].Type, specialSymbols.CancellationTokenSymbol))
{
continue;
}

ValidateType(context, method.Parameters[i].Type, method.Parameters[i].Locations[0], supportTypeSymbols, transpilationSourceAttributeSymbol);
}
}
}

private static bool IsLast(int index, int length) => index == length - 1;

private static void ValidateType(
SymbolAnalysisContext context,
ITypeSymbol typeSymbol,
Expand Down Expand Up @@ -399,7 +409,7 @@ private static void ValidateReceiverReturnType(
{
var typeArg = namedReturnTypeSymbol.TypeArguments[0];
ValidateType(context, typeArg, location, supportTypeSymbols, transpilationSourceAttribute);

return;
}

Expand Down
4 changes: 2 additions & 2 deletions src/TypedSignalR.Client.TypeScript/Templates/ApiTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public constructor(
this.Write(" const __");
this.Write(this.ToStringHelper.ToStringWithCulture(method.Name.Format(Options.NamingStyle)));
this.Write(" = ");
this.Write(this.ToStringHelper.ToStringWithCulture(method.WrapLambdaExpressionSyntax(Options)));
this.Write(this.ToStringHelper.ToStringWithCulture(method.TranslateReceiverMethodIntoLambdaExpressionSyntax(SpecialSymbols, Options)));
this.Write(";\r\n");
}
this.Write("\r\n");
Expand Down Expand Up @@ -184,7 +184,7 @@ public class ApiTemplateBase
/// <summary>
/// The string builder that generation-time code is using to assemble generated output
/// </summary>
protected System.Text.StringBuilder GenerationEnvironment
public System.Text.StringBuilder GenerationEnvironment
{
get
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class <#= receiverType.Name #>_Binder implements ReceiverRegister<<#= receiverTy
public readonly register = (connection: HubConnection, receiver: <#= receiverType.Name #>): Disposable => {

<# foreach(var method in receiverType.Methods) { #>
const __<#= method.Name.Format(Options.NamingStyle) #> = <#= method.WrapLambdaExpressionSyntax(Options) #>;
const __<#= method.Name.Format(Options.NamingStyle) #> = <#= method.TranslateReceiverMethodIntoLambdaExpressionSyntax(SpecialSymbols, Options) #>;
<# } #>

<# foreach(var method in receiverType.Methods) { #>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,34 @@ namespace TypedSignalR.Client.TypeScript.Templates;

internal static class MethodSymbolExtensions
{
public static string WrapLambdaExpressionSyntax(this IMethodSymbol methodSymbol, ITypedSignalRTranspilationOptions options)
public static string TranslateReceiverMethodIntoLambdaExpressionSyntax(this IMethodSymbol receiverMethodSymbol, SpecialSymbols specialSymbols, ITypedSignalRTranspilationOptions options)
{
if (methodSymbol.Parameters.Length == 0)
if (receiverMethodSymbol.Parameters.Length == 0)
{
return $"() => receiver.{methodSymbol.Name.Format(options.MethodStyle)}()";
return $"() => receiver.{receiverMethodSymbol.Name.Format(options.MethodStyle)}()";
}

var parameters = ParametersToTypeArray(methodSymbol, options);
return $"(...args: {parameters}) => receiver.{methodSymbol.Name.Format(options.MethodStyle)}(...args)";
if (receiverMethodSymbol.Parameters.Length == 1
// Ignore if the last parameter of a receiver's method is a CancellationToken.
&& SymbolEqualityComparer.Default.Equals(receiverMethodSymbol.Parameters[0].Type, specialSymbols.CancellationTokenSymbol))
{
return $"() => receiver.{receiverMethodSymbol.Name.Format(options.MethodStyle)}()";
}

var parameters = ParametersToTypeArray(receiverMethodSymbol, specialSymbols, options);

return $"(...args: {parameters}) => receiver.{receiverMethodSymbol.Name.Format(options.MethodStyle)}(...args)";

static string ParametersToTypeArray(IMethodSymbol methodSymbol, SpecialSymbols specialSymbols, ITypedSignalRTranspilationOptions options)
{
var methodParameters = SymbolEqualityComparer.Default.Equals(methodSymbol.Parameters.Last().Type, specialSymbols.CancellationTokenSymbol)
? methodSymbol.Parameters.SkipLast(1) // Ignore if the last parameter of a receiver's method is a CancellationToken.
: methodSymbol.Parameters;

var parameters = methodParameters.Select(x => TypeMapper.MapTo(x.Type, options));

return $"[{string.Join(", ", parameters)}]";
}
}

public static string CreateMethodString(this IMethodSymbol methodSymbol, SpecialSymbols specialSymbols, ITypedSignalRTranspilationOptions options)
Expand Down Expand Up @@ -78,12 +97,6 @@ private static string ReturnTypeToTypeScriptString(this IMethodSymbol methodSymb
return TypeMapper.MapTo(methodSymbol.ReturnType, options);
}

private static string ParametersToTypeArray(IMethodSymbol methodSymbol, ITypedSignalRTranspilationOptions options)
{
var parameters = methodSymbol.Parameters.Select(x => TypeMapper.MapTo(x.Type, options));
return $"[{string.Join(", ", parameters)}]";
}

private static string CreateUnaryMethodString(IMethodSymbol methodSymbol, SpecialSymbols specialSymbols, ITypedSignalRTranspilationOptions options)
{
var name = methodSymbol.Name.Format(options.MethodStyle);
Expand Down
4 changes: 2 additions & 2 deletions tests/TypeScriptTests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"test": "jest"
},
"dependencies": {
"@microsoft/signalr": "^8.0.0",
"@microsoft/signalr-protocol-msgpack": "^8.0.0"
"@microsoft/signalr": "^8.0.7",
"@microsoft/signalr-protocol-msgpack": "^8.0.7"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { HubConnectionBuilder } from '@microsoft/signalr'
import { getHubProxyFactory, getReceiverRegister } from '../generated/json/TypedSignalR.Client'
import { UserDefinedType } from '../generated/json/TypedSignalR.Client.TypeScript.Tests.Shared';
import { IReceiverWithCancellationToken } from '../generated/json/TypedSignalR.Client/TypedSignalR.Client.TypeScript.Tests.Shared';

const toUTCString = (date: string | Date): string => {
if (typeof date === 'string') {
const d = new Date(date);
return d.toUTCString();
}

return date.toUTCString();
}

const answerMessages: string[] = [
"b1f7cd73-13b8-49bd-9557-ffb38859d18b",
"3f5c3585-d01b-4f8f-8139-62a1241850e2",
"92021a22-5823-4501-8cbd-c20d4ca6e54c",
"5b134f73-2dc1-4271-8316-1a4250f42241",
"e73acd30-e034-4569-8f30-88ac34b99052",
"0d7531b5-0a36-4fe7-bbe5-8fee38c38c07",
"32915627-3df6-41dc-8d30-7c655c2f7e61",
"c875a6f9-9ddb-440b-a7e4-6e893f59ab9e",
];

const guids: string[] = [
"b2f626e5-b4d4-4713-891d-f6cb107e502e",
"22733524-2087-4701-a586-c6bf0ce36f74",
"b89324bf-daf2-422a-85f2-6843b9c09b6a",
"779769d1-0aee-4dba-82c7-9e1044836d75"
];

const dateTimes: string[] = [
"2017-04-17",
"2018-05-25",
"2019-03-31",
"2022-02-06",
];

const testMethod = async () => {
const connection = new HubConnectionBuilder()
.withUrl("http://localhost:5000/hubs/receiverTestWithCancellationTokenHub")
.build();

const receiveMessageList: [string, number][] = [];
const userDefinedList: UserDefinedType[] = [];
let notifyCallCount = 0;

const receiver: IReceiverWithCancellationToken = {
receiveMessage: (message: string, value: number): Promise<void> => {
receiveMessageList.push([message, value]);
return Promise.resolve();
},
notify: (): Promise<void> => {
notifyCallCount += 1;
return Promise.resolve();
},
receiveCustomMessage: (userDefined: UserDefinedType): Promise<void> => {
userDefinedList.push(userDefined)
return Promise.resolve();
}
}

const hubProxy = getHubProxyFactory("IReceiverTestHub")
.createHubProxy(connection);

const subscription = getReceiverRegister("IReceiverWithCancellationToken")
.register(connection, receiver);

try {
await connection.start();
await hubProxy.start();

expect(notifyCallCount).toEqual(17);

for (let i = 0; i < receiveMessageList.length; i++) {
expect(receiveMessageList[i][0]).toEqual(answerMessages[i]);
expect(receiveMessageList[i][1]).toEqual(i);
}

for (let i = 0; i < userDefinedList.length; i++) {
expect(userDefinedList[i].guid).toEqual(guids[i]);
expect(toUTCString(userDefinedList[i].dateTime)).toEqual(toUTCString(dateTimes[i]));
}
}
finally {
subscription.dispose();
await connection.stop()
}
}

test('receiver.test', testMethod);
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { HubConnectionBuilder } from '@microsoft/signalr'
import { MessagePackHubProtocol } from '@microsoft/signalr-protocol-msgpack';
import { getHubProxyFactory, getReceiverRegister } from '../generated/msgpack/TypedSignalR.Client'
import { UserDefinedType } from '../generated/msgpack/TypedSignalR.Client.TypeScript.Tests.Shared';
import { IReceiverWithCancellationToken } from '../generated/msgpack/TypedSignalR.Client/TypedSignalR.Client.TypeScript.Tests.Shared';

const toUTCString = (date: string | Date): string => {
if (typeof date === 'string') {
const d = new Date(date);
return d.toUTCString();
}

return date.toUTCString();
}

const answerMessages: string[] = [
"b1f7cd73-13b8-49bd-9557-ffb38859d18b",
"3f5c3585-d01b-4f8f-8139-62a1241850e2",
"92021a22-5823-4501-8cbd-c20d4ca6e54c",
"5b134f73-2dc1-4271-8316-1a4250f42241",
"e73acd30-e034-4569-8f30-88ac34b99052",
"0d7531b5-0a36-4fe7-bbe5-8fee38c38c07",
"32915627-3df6-41dc-8d30-7c655c2f7e61",
"c875a6f9-9ddb-440b-a7e4-6e893f59ab9e",
];

const guids: string[] = [
"b2f626e5-b4d4-4713-891d-f6cb107e502e",
"22733524-2087-4701-a586-c6bf0ce36f74",
"b89324bf-daf2-422a-85f2-6843b9c09b6a",
"779769d1-0aee-4dba-82c7-9e1044836d75"
];

const dateTimes: string[] = [
"2017-04-17",
"2018-05-25",
"2019-03-31",
"2022-02-06",
];

const testMethod = async () => {
const connection = new HubConnectionBuilder()
.withUrl("http://localhost:5000/hubs/receiverTestWithCancellationTokenHub")
.withHubProtocol(new MessagePackHubProtocol())
.build();

const receiveMessageList: [string, number][] = [];
const userDefinedList: UserDefinedType[] = [];
let notifyCallCount = 0;

const receiver: IReceiverWithCancellationToken = {
receiveMessage: (message: string, value: number): Promise<void> => {
receiveMessageList.push([message, value]);
return Promise.resolve();
},
notify: (): Promise<void> => {
notifyCallCount += 1;
return Promise.resolve();
},
receiveCustomMessage: (userDefined: UserDefinedType): Promise<void> => {
userDefinedList.push(userDefined)
return Promise.resolve();
}
}

const hubProxy = getHubProxyFactory("IReceiverTestHub")
.createHubProxy(connection);

const subscription = getReceiverRegister("IReceiverWithCancellationToken")
.register(connection, receiver);

try {
await connection.start();
await hubProxy.start();

expect(notifyCallCount).toEqual(17);

for (let i = 0; i < receiveMessageList.length; i++) {
expect(receiveMessageList[i][0]).toEqual(answerMessages[i]);
expect(receiveMessageList[i][1]).toEqual(i);
}

for (let i = 0; i < userDefinedList.length; i++) {
expect(userDefinedList[i].Guid).toEqual(guids[i]);
expect(toUTCString(userDefinedList[i].DateTime)).toEqual(toUTCString(dateTimes[i]));
}
}
finally {
subscription.dispose();
await connection.stop();
}
}

test('receiver.test', testMethod);
18 changes: 9 additions & 9 deletions tests/TypeScriptTests/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1205,18 +1205,18 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"

"@microsoft/signalr-protocol-msgpack@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-8.0.0.tgz#cc1777c8614e2586d890f1a7ea47637f1cd42ffe"
integrity sha512-XtN5lUPVOtU96aqpB6z00o0TQayx5fmcf7CeQKDXF1flg8G96wtNCFXKb/p4sM/nvprjSmz0JiWQfc1TVXsa6Q==
"@microsoft/signalr-protocol-msgpack@^8.0.7":
version "8.0.7"
resolved "https://registry.yarnpkg.com/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-8.0.7.tgz#54302f89883831f84531962d6c1944e790c510fd"
integrity sha512-yrGt0E9l8X9HSF9P8gmnkVi+IDGNP6seU5/YAiFzIisH4VpvayVcf7Z1Lbu7nNwM/GzFgtFIa7jBivLQCCcyEQ==
dependencies:
"@microsoft/signalr" ">=8.0.0"
"@microsoft/signalr" ">=8.0.7"
"@msgpack/msgpack" "^2.7.0"

"@microsoft/signalr@>=8.0.0", "@microsoft/signalr@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-8.0.0.tgz#cb1412e88e0527f40da9178fefc27a65c3ddeab0"
integrity sha512-K/wS/VmzRWePCGqGh8MU8OWbS1Zvu7DG7LSJS62fBB8rJUXwwj4axQtqrAAwKGUZHQF6CuteuQR9xMsVpM2JNA==
"@microsoft/signalr@>=8.0.7", "@microsoft/signalr@^8.0.7":
version "8.0.7"
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-8.0.7.tgz#94419ddbf9418753e493f4ae4c13990316ec2ea5"
integrity sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==
dependencies:
abort-controller "^3.0.0"
eventsource "^2.0.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Globalization;
using Microsoft.AspNetCore.SignalR;
using TypedSignalR.Client.TypeScript.Tests.Shared;

Expand Down
Loading

0 comments on commit 75f539a

Please sign in to comment.