Skip to content

Commit

Permalink
Factored out method handler
Browse files Browse the repository at this point in the history
  • Loading branch information
LPeter1997 committed Oct 20, 2023
1 parent 8da7cf1 commit b7ddd5c
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 201 deletions.
2 changes: 1 addition & 1 deletion src/Draco.Dap/Adapter/DebugAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private static void RegisterAdapterRpcMethods(object target, IJsonRpcConnection

foreach (var method in adapterMethods)
{
connection.AddHandler(new DebugAdapterMethodHandler(method, target));
connection.AddHandler(DebugAdapterMethodHandler.Create(method, target));
}
}

Expand Down
113 changes: 13 additions & 100 deletions src/Draco.Dap/Adapter/DebugAdapterMethodHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,115 +9,28 @@

namespace Draco.Dap.Adapter;

internal sealed class DebugAdapterMethodHandler : IJsonRpcMethodHandler
internal static class DebugAdapterMethodHandler
{
private static readonly MethodInfo taskGetResult = typeof(Task<>).GetMethod("get_Result")!;

public string MethodName { get; }
public bool IsRequest { get; }
public bool IsNotification => !this.IsRequest;
public bool AcceptsParams => this.DeclaredParamsType is not null;
public bool SupportsCancellation { get; }
public bool Mutating { get; }
public Type? DeclaredParamsType { get; }
public Type DeclaredReturnType { get; }

private readonly MethodInfo handlerMethod;
private readonly object? target;

public DebugAdapterMethodHandler(MethodInfo handlerMethod, object? target)
public static IJsonRpcMethodHandler Create(MethodInfo handlerMethod, object? target)
{
this.handlerMethod = handlerMethod;
this.target = target;

// Verify parameters
var parameters = handlerMethod.GetParameters();
switch (parameters.Length)
{
case 2:
{
if (parameters[^1].ParameterType != typeof(CancellationToken))
{
throw new ArgumentException("The second parameter of a handler must be CancellationToken if it is defined.", nameof(handlerMethod));
}

this.SupportsCancellation = true;
this.DeclaredParamsType = parameters[0].ParameterType;
break;
}
case 1:
{
var type = parameters[0].ParameterType;
if (type == typeof(CancellationToken))
{
this.SupportsCancellation = true;
}
else
{
this.DeclaredParamsType = type;
}

break;
}
case 0:
{
// No cancellation, no parameters
break;
}
default:
throw new ArgumentException("Handler has too many arguments.", nameof(handlerMethod));
}

// Verify attributes
var requestAttr = handlerMethod.GetCustomAttribute<RequestAttribute>();
var notificationAttr = handlerMethod.GetCustomAttribute<EventAttribute>();

if (requestAttr is null && notificationAttr is null)
{
throw new ArgumentException($"Handler must be marked as either a request or notification handler.", nameof(handlerMethod));
}

if (requestAttr is not null && notificationAttr is not null)
{
throw new ArgumentException($"Handler can not be marked as both a request and notification handler.", nameof(handlerMethod));
}

this.MethodName = requestAttr?.Method ?? notificationAttr!.Method;
this.Mutating = requestAttr?.Mutating ?? notificationAttr!.Mutating;
var eventAttr = handlerMethod.GetCustomAttribute<EventAttribute>();

this.IsRequest = requestAttr is not null;

// Verify return type
var returnType = handlerMethod.ReturnType;
if (this.IsRequest && returnType != typeof(Task) && returnType.GetGenericTypeDefinition() != typeof(Task<>))
if (requestAttr is null && eventAttr is null)
{
throw new ArgumentException("Request handler must return Task or Task<T>.", nameof(handlerMethod));
throw new ArgumentException("Handler must be marked as either a request or event handler.", nameof(handlerMethod));
}

if (!this.IsRequest && returnType != typeof(Task))
if (requestAttr is not null && eventAttr is not null)
{
throw new ArgumentException("Notification handler must return Task.", nameof(handlerMethod));
throw new ArgumentException("Handler can not be marked as both a request and event handler.", nameof(handlerMethod));
}

this.DeclaredReturnType = returnType;
}

public Task InvokeNotification(object?[] args) =>
(Task)this.handlerMethod.Invoke(this.target, args.ToArray())!;

public async Task<object?> InvokeRequest(object?[] args)
{
var task = (Task)this.handlerMethod.Invoke(this.target, args.ToArray())!;
await task;

if (this.DeclaredReturnType == typeof(Task))
{
return null;
}
else
{
var getResult = (MethodInfo)task.GetType().GetMemberWithSameMetadataDefinitionAs(taskGetResult);
return getResult.Invoke(task, null);
}
return JsonRpcMethodHandler.Create(
handlerMethod: handlerMethod,
target: target,
methodName: requestAttr?.Method ?? eventAttr!.Method,
isRequest: requestAttr is not null,
isMutating: requestAttr?.Mutating ?? eventAttr!.Mutating);
}
}
2 changes: 1 addition & 1 deletion src/Draco.Dap/Adapter/DebugClientProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ namespace Draco.Dap.Adapter;
internal class DebugClientProxy : JsonRpcClientProxy
{
protected override IJsonRpcMethodHandler CreateHandler(MethodInfo method) =>
new DebugAdapterMethodHandler(method, this);
DebugAdapterMethodHandler.Create(method, this);
}
133 changes: 133 additions & 0 deletions src/Draco.JsonRpc/JsonRpcMethodHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Draco.JsonRpc;

/// <summary>
/// Utilities for construction <see cref="IJsonRpcMethodHandler"/>s.
/// </summary>
public static class JsonRpcMethodHandler
{
/// <summary>
/// Constructs a new <see cref="IJsonRpcMethodHandler"/> from a given <see cref="MethodInfo"/>.
/// </summary>
/// <param name="handlerMethod">The handler method info.</param>
/// <param name="target">The invocation target.</param>
/// <param name="methodName">The invoked RPC method name.</param>
/// <param name="isRequest">True, if this is a request handler, false if this is a notification handler.</param>
/// <param name="isMutating">True, if the method has mutating semantics associated, false otherwise.</param>
/// <returns>The constructed handler.</returns>
public static IJsonRpcMethodHandler Create(
MethodInfo handlerMethod,
object? target,
string methodName,
bool isRequest,
bool isMutating) => new MethodInfoHandler(handlerMethod, target, methodName, isRequest, isMutating);

private sealed class MethodInfoHandler : IJsonRpcMethodHandler
{
private static readonly MethodInfo taskGetResult = typeof(Task<>).GetMethod("get_Result")!;

public string MethodName { get; }
public bool IsRequest { get; }
public bool IsNotification => !this.IsRequest;
public bool AcceptsParams => this.DeclaredParamsType is not null;
public bool SupportsCancellation { get; }
public bool Mutating { get; }
public Type? DeclaredParamsType { get; }
public Type DeclaredReturnType { get; }

private readonly MethodInfo handlerMethod;
private readonly object? target;

public MethodInfoHandler(
MethodInfo handlerMethod,
object? target,
string methodName,
bool isRequest,
bool isMutating)
{
this.MethodName = methodName;
this.IsRequest = isRequest;
this.Mutating = isMutating;

this.handlerMethod = handlerMethod;
this.target = target;

// Verify parameters
var parameters = handlerMethod.GetParameters();
switch (parameters.Length)
{
case 2:
{
if (parameters[^1].ParameterType != typeof(CancellationToken))
{
throw new ArgumentException("The second parameter of a handler must be CancellationToken if it is defined.", nameof(handlerMethod));
}

this.SupportsCancellation = true;
this.DeclaredParamsType = parameters[0].ParameterType;
break;
}
case 1:
{
var type = parameters[0].ParameterType;
if (type == typeof(CancellationToken))
{
this.SupportsCancellation = true;
}
else
{
this.DeclaredParamsType = type;
}

break;
}
case 0:
{
// No cancellation, no parameters
break;
}
default:
throw new ArgumentException("Handler has too many arguments.", nameof(handlerMethod));
}

// Verify return type
var returnType = handlerMethod.ReturnType;
if (this.IsRequest && returnType != typeof(Task) && returnType.GetGenericTypeDefinition() != typeof(Task<>))
{
throw new ArgumentException("Request handler must return Task or Task<T>.", nameof(handlerMethod));
}

if (!this.IsRequest && returnType != typeof(Task))
{
throw new ArgumentException("Notification handler must return Task.", nameof(handlerMethod));
}

this.DeclaredReturnType = returnType;
}

public Task InvokeNotification(object?[] args) =>
(Task)this.handlerMethod.Invoke(this.target, args.ToArray())!;

public async Task<object?> InvokeRequest(object?[] args)
{
var task = (Task)this.handlerMethod.Invoke(this.target, args.ToArray())!;
await task;

if (this.DeclaredReturnType == typeof(Task))
{
return null;
}
else
{
var getResult = (MethodInfo)task.GetType().GetMemberWithSameMetadataDefinitionAs(taskGetResult);
return getResult.Invoke(task, null);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Draco.Lsp/Server/LanguageClientProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ namespace Draco.Lsp.Server;
internal class LanguageClientProxy : JsonRpcClientProxy
{
protected override IJsonRpcMethodHandler CreateHandler(MethodInfo method) =>
new LanguageServerMethodHandler(method, this);
LanguageServerMethodHandler.Create(method, this);
}
2 changes: 1 addition & 1 deletion src/Draco.Lsp/Server/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private static void RegisterServerRpcMethods(object target, IJsonRpcConnection c

foreach (var method in langserverMethods)
{
connection.AddHandler(new LanguageServerMethodHandler(method, target));
connection.AddHandler(LanguageServerMethodHandler.Create(method, target));
}
}

Expand Down
Loading

0 comments on commit b7ddd5c

Please sign in to comment.