Skip to content

Move payload deserialisation out of handlers #53

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

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions src/Client/Dispatcher/LspDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Client.Handlers;

namespace OmniSharp.Extensions.LanguageServer.Client.Dispatcher
Expand All @@ -21,10 +22,22 @@ public class LspDispatcher
/// <summary>
/// Create a new <see cref="LspDispatcher"/>.
/// </summary>
public LspDispatcher()
/// <param name="serializer">
/// The JSON serialiser for notification / request / response payloads.
/// </param>
public LspDispatcher(ISerializer serializer)
{
if (serializer == null)
throw new ArgumentNullException(nameof(serializer));

Serializer = serializer;
}

/// <summary>
/// The JSON serialiser to use for notification / request / response payloads.
/// </summary>
public ISerializer Serializer { get; set; }

/// <summary>
/// Register a handler invoker.
/// </summary>
Expand Down Expand Up @@ -92,7 +105,9 @@ public async Task<bool> TryHandleNotification(string method, JObject notificatio

if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeNotificationHandler notificationHandler)
{
await notificationHandler.Invoke(notification);
object notificationPayload = DeserializePayload(notificationHandler.BodyType, notification);

await notificationHandler.Invoke(notificationPayload);

return true;
}
Expand Down Expand Up @@ -121,9 +136,36 @@ public Task<object> TryHandleRequest(string method, JObject request, Cancellatio
throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(method)}.", nameof(method));

if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeRequestHandler requestHandler)
return requestHandler.Invoke(request, cancellationToken);
{
object requestPayload = DeserializePayload(requestHandler.BodyType, request);

return requestHandler.Invoke(requestPayload, cancellationToken);
}

return null;
}

/// <summary>
/// Deserialise a notification / request payload from JSON.
/// </summary>
/// <param name="payloadType">
/// The payload's CLR type.
/// </param>
/// <param name="payload">
/// JSON representing the payload.
/// </param>
/// <returns>
/// The deserialised payload (if one is present and expected).
/// </returns>
object DeserializePayload(Type payloadType, JObject payload)
{
if (payloadType == null)
throw new ArgumentNullException(nameof(payloadType));

if (payloadType == null || payload == null)
return null;

return payload.ToObject(payloadType, Serializer.JsonSerializer);
}
}
}
5 changes: 5 additions & 0 deletions src/Client/Handlers/DelegateEmptyNotificationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public DelegateEmptyNotificationHandler(string method, NotificationHandler handl
/// </summary>
public NotificationHandler Handler { get; }

/// <summary>
/// The expected CLR type of the notification body (<c>null</c>, since the handler does not use the request body).
/// </summary>
public override Type BodyType => null;

/// <summary>
/// Invoke the handler.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Client/Handlers/DelegateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,10 @@ protected DelegateHandler(string method)
/// The name of the method handled by the handler.
/// </summary>
public string Method { get; }

/// <summary>
/// The expected CLR type of the request / notification body (if any; <c>null</c> if the handler does not use the request body).
/// </summary>
public abstract Type BodyType { get; }
}
}
9 changes: 7 additions & 2 deletions src/Client/Handlers/DelegateNotificationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public DelegateNotificationHandler(string method, NotificationHandler<TNotificat
/// </summary>
public NotificationHandler<TNotification> Handler { get; }

/// <summary>
/// The expected CLR type of the notification body.
/// </summary>
public override Type BodyType => typeof(TNotification);

/// <summary>
/// Invoke the handler.
/// </summary>
Expand All @@ -47,12 +52,12 @@ public DelegateNotificationHandler(string method, NotificationHandler<TNotificat
/// <returns>
/// A <see cref="Task"/> representing the operation.
/// </returns>
public async Task Invoke(JObject notification)
public async Task Invoke(object notification)
{
await Task.Yield();

Handler(
notification != null ? notification.ToObject<TNotification>(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TNotification)
(TNotification)notification
);
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/Client/Handlers/DelegateRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public DelegateRequestHandler(string method, RequestHandler<TRequest> handler)
/// </summary>
public RequestHandler<TRequest> Handler { get; }

/// <summary>
/// The expected CLR type of the request body.
/// </summary>
public override Type BodyType => typeof(TRequest);

/// <summary>
/// Invoke the handler.
/// </summary>
Expand All @@ -51,10 +56,10 @@ public DelegateRequestHandler(string method, RequestHandler<TRequest> handler)
/// <returns>
/// A <see cref="Task{TResult}"/> representing the operation.
/// </returns>
public async Task<object> Invoke(JObject request, CancellationToken cancellationToken)
public async Task<object> Invoke(object request, CancellationToken cancellationToken)
{
await Handler(
request != null ? request.ToObject<TRequest>(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TRequest),
(TRequest)request,
cancellationToken
);

Expand Down
9 changes: 7 additions & 2 deletions src/Client/Handlers/DelegateRequestResponseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public DelegateRequestResponseHandler(string method, RequestHandler<TRequest, TR
/// </summary>
public RequestHandler<TRequest, TResponse> Handler { get; }

/// <summary>
/// The expected CLR type of the request body.
/// </summary>
public override Type BodyType => typeof(TRequest);

/// <summary>
/// Invoke the handler.
/// </summary>
Expand All @@ -54,10 +59,10 @@ public DelegateRequestResponseHandler(string method, RequestHandler<TRequest, TR
/// <returns>
/// A <see cref="Task{TResult}"/> representing the operation.
/// </returns>
public async Task<object> Invoke(JObject request, CancellationToken cancellationToken)
public async Task<object> Invoke(object request, CancellationToken cancellationToken)
{
return await Handler(
request != null ? request.ToObject<TRequest>(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TRequest),
(TRequest)request,
cancellationToken
);
}
Expand Down
8 changes: 7 additions & 1 deletion src/Client/Handlers/DynamicRegistrationHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -31,6 +32,11 @@ public DynamicRegistrationHandler()
/// </summary>
public string Method => "client/registerCapability";

/// <summary>
/// The expected CLR type of the request / notification body (if any; <c>null</c> if the handler does not use the request body).
/// </summary>
public Type BodyType => null;

/// <summary>
/// Invoke the handler.
/// </summary>
Expand All @@ -43,7 +49,7 @@ public DynamicRegistrationHandler()
/// <returns>
/// A <see cref="Task{TResult}"/> representing the operation.
/// </returns>
public Task<object> Invoke(JObject request, CancellationToken cancellationToken)
public Task<object> Invoke(object request, CancellationToken cancellationToken)
{
// For now, we don't really support dynamic registration but OmniSharp's implementation sends a request even when dynamic registrations are not supported.

Expand Down
9 changes: 8 additions & 1 deletion src/Client/Handlers/IHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace OmniSharp.Extensions.LanguageServer.Client.Handlers
using System;

namespace OmniSharp.Extensions.LanguageServer.Client.Handlers
{
/// <summary>
/// Represents a client-side message handler.
Expand All @@ -9,5 +11,10 @@ public interface IHandler
/// The name of the method handled by the handler.
/// </summary>
string Method { get; }

/// <summary>
/// The expected CLR type of the request / notification body (if any; <c>null</c> if the handler does not use the request body).
/// </summary>
Type BodyType { get; }
}
}
2 changes: 1 addition & 1 deletion src/Client/Handlers/IInvokeNotificationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public interface IInvokeNotificationHandler
/// <returns>
/// A <see cref="Task"/> representing the operation.
/// </returns>
Task Invoke(JObject notification);
Task Invoke(object notification);
}
}
2 changes: 1 addition & 1 deletion src/Client/Handlers/IInvokeRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public interface IInvokeRequestHandler
/// <returns>
/// A <see cref="Task{TResult}"/> representing the operation.
/// </returns>
Task<object> Invoke(JObject request, CancellationToken cancellationToken);
Task<object> Invoke(object request, CancellationToken cancellationToken);
}
}
5 changes: 5 additions & 0 deletions src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public JsonRpcEmptyNotificationHandler(string method, INotificationHandler handl
/// </summary>
public INotificationHandler Handler { get; }

/// <summary>
/// The expected CLR type of the notification body (<c>null</c>, since the handler does not use the request body).
/// </summary>
public override Type BodyType => null;

/// <summary>
/// Invoke the handler.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Client/Handlers/JsonRpcHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,10 @@ protected JsonRpcHandler(string method)
/// The name of the method handled by the handler.
/// </summary>
public string Method { get; }

/// <summary>
/// The expected CLR type of the request / notification body (if any; <c>null</c> if the handler does not use the request body).
/// </summary>
public abstract Type BodyType { get; }
}
}
10 changes: 7 additions & 3 deletions src/Client/Handlers/JsonRpcNotificationHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
Expand Down Expand Up @@ -39,6 +38,11 @@ public JsonRpcNotificationHandler(string method, INotificationHandler<TNotificat
/// </summary>
public INotificationHandler<TNotification> Handler { get; }

/// <summary>
/// The expected CLR type of the notification body.
/// </summary>
public override Type BodyType => typeof(TNotification);

/// <summary>
/// Invoke the handler.
/// </summary>
Expand All @@ -48,8 +52,8 @@ public JsonRpcNotificationHandler(string method, INotificationHandler<TNotificat
/// <returns>
/// A <see cref="Task"/> representing the operation.
/// </returns>
public Task Invoke(JObject notification) => Handler.Handle(
notification != null ? notification.ToObject<TNotification>(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TNotification)
public Task Invoke(object notification) => Handler.Handle(
(TNotification)notification
);
}
}
13 changes: 12 additions & 1 deletion src/Client/LanguageClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Client.Clients;
using OmniSharp.Extensions.LanguageServer.Client.Dispatcher;
using OmniSharp.Extensions.LanguageServer.Client.Handlers;
using OmniSharp.Extensions.LanguageServer.Client.Processes;
using OmniSharp.Extensions.LanguageServer.Client.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities;

namespace OmniSharp.Extensions.LanguageServer.Client
Expand All @@ -23,10 +25,18 @@ namespace OmniSharp.Extensions.LanguageServer.Client
public sealed class LanguageClient
: IDisposable
{
/// <summary>
/// The serialiser for notification / request / response bodies.
/// </summary>
/// <remarks>
/// TODO: Make this injectable. And what does client version do - do we have to negotiate this?
/// </remarks>
readonly ISerializer _serializer = new Serializer(ClientVersion.Lsp3);

/// <summary>
/// The dispatcher for incoming requests, notifications, and responses.
/// </summary>
readonly LspDispatcher _dispatcher = new LspDispatcher();
readonly LspDispatcher _dispatcher;

/// <summary>
/// The handler for dynamic registration of server capabilities.
Expand Down Expand Up @@ -101,6 +111,7 @@ public LanguageClient(ILoggerFactory loggerFactory, ServerProcess process)
Window = new WindowClient(this);
TextDocument = new TextDocumentClient(this);

_dispatcher = new LspDispatcher(_serializer);
_dispatcher.RegisterHandler(_dynamicRegistrationHandler);
}

Expand Down
Loading