From 64c732cd665552e52da988db7c5dd89520d60114 Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Tue, 19 Dec 2017 18:45:43 +1100 Subject: [PATCH 1/4] Move payload deserialisation out of handlers. This is so the common JsonSerializerSettings can be used by the calling code. NOTE: This code is currently non-functional - handlers have been modified to take an Object instead of a JObject but the calling code does not yet perform deserialisation. --- .../Handlers/DelegateEmptyNotificationHandler.cs | 5 +++++ src/Client/Handlers/DelegateHandler.cs | 5 +++++ src/Client/Handlers/DelegateNotificationHandler.cs | 9 +++++++-- src/Client/Handlers/DelegateRequestHandler.cs | 9 +++++++-- src/Client/Handlers/DelegateRequestResponseHandler.cs | 9 +++++++-- src/Client/Handlers/DynamicRegistrationHandler.cs | 8 +++++++- src/Client/Handlers/IHandler.cs | 9 ++++++++- src/Client/Handlers/IInvokeNotificationHandler.cs | 2 +- src/Client/Handlers/IInvokeRequestHandler.cs | 2 +- src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs | 5 +++++ src/Client/Handlers/JsonRpcHandler.cs | 5 +++++ src/Client/Handlers/JsonRpcNotificationHandler.cs | 10 +++++++--- 12 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/Client/Handlers/DelegateEmptyNotificationHandler.cs b/src/Client/Handlers/DelegateEmptyNotificationHandler.cs index a0d164d44..dd8cd4e1a 100644 --- a/src/Client/Handlers/DelegateEmptyNotificationHandler.cs +++ b/src/Client/Handlers/DelegateEmptyNotificationHandler.cs @@ -32,6 +32,11 @@ public DelegateEmptyNotificationHandler(string method, NotificationHandler handl /// public NotificationHandler Handler { get; } + /// + /// The expected CLR type of the notification body (null, since the handler does not use the request body). + /// + public override Type BodyType => null; + /// /// Invoke the handler. /// diff --git a/src/Client/Handlers/DelegateHandler.cs b/src/Client/Handlers/DelegateHandler.cs index 3813e4288..a0bf12025 100644 --- a/src/Client/Handlers/DelegateHandler.cs +++ b/src/Client/Handlers/DelegateHandler.cs @@ -26,5 +26,10 @@ protected DelegateHandler(string method) /// The name of the method handled by the handler. /// public string Method { get; } + + /// + /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// + public abstract Type BodyType { get; } } } diff --git a/src/Client/Handlers/DelegateNotificationHandler.cs b/src/Client/Handlers/DelegateNotificationHandler.cs index 953ae4ef7..1a537cd96 100644 --- a/src/Client/Handlers/DelegateNotificationHandler.cs +++ b/src/Client/Handlers/DelegateNotificationHandler.cs @@ -38,6 +38,11 @@ public DelegateNotificationHandler(string method, NotificationHandler public NotificationHandler Handler { get; } + /// + /// The expected CLR type of the notification body. + /// + public override Type BodyType => typeof(TNotification); + /// /// Invoke the handler. /// @@ -47,12 +52,12 @@ public DelegateNotificationHandler(string method, NotificationHandler /// A representing the operation. /// - public async Task Invoke(JObject notification) + public async Task Invoke(object notification) { await Task.Yield(); Handler( - notification != null ? notification.ToObject(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TNotification) + (TNotification)notification ); } } diff --git a/src/Client/Handlers/DelegateRequestHandler.cs b/src/Client/Handlers/DelegateRequestHandler.cs index 706e2c5c3..5f4567b60 100644 --- a/src/Client/Handlers/DelegateRequestHandler.cs +++ b/src/Client/Handlers/DelegateRequestHandler.cs @@ -39,6 +39,11 @@ public DelegateRequestHandler(string method, RequestHandler handler) /// public RequestHandler Handler { get; } + /// + /// The expected CLR type of the request body. + /// + public override Type BodyType => typeof(TRequest); + /// /// Invoke the handler. /// @@ -51,10 +56,10 @@ public DelegateRequestHandler(string method, RequestHandler handler) /// /// A representing the operation. /// - public async Task Invoke(JObject request, CancellationToken cancellationToken) + public async Task Invoke(object request, CancellationToken cancellationToken) { await Handler( - request != null ? request.ToObject(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TRequest), + (TRequest)request, cancellationToken ); diff --git a/src/Client/Handlers/DelegateRequestResponseHandler.cs b/src/Client/Handlers/DelegateRequestResponseHandler.cs index 43095e74b..6daea19c8 100644 --- a/src/Client/Handlers/DelegateRequestResponseHandler.cs +++ b/src/Client/Handlers/DelegateRequestResponseHandler.cs @@ -42,6 +42,11 @@ public DelegateRequestResponseHandler(string method, RequestHandler public RequestHandler Handler { get; } + /// + /// The expected CLR type of the request body. + /// + public override Type BodyType => typeof(TRequest); + /// /// Invoke the handler. /// @@ -54,10 +59,10 @@ public DelegateRequestResponseHandler(string method, RequestHandler /// A representing the operation. /// - public async Task Invoke(JObject request, CancellationToken cancellationToken) + public async Task Invoke(object request, CancellationToken cancellationToken) { return await Handler( - request != null ? request.ToObject(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TRequest), + (TRequest)request, cancellationToken ); } diff --git a/src/Client/Handlers/DynamicRegistrationHandler.cs b/src/Client/Handlers/DynamicRegistrationHandler.cs index 188f135b9..ec21057ea 100644 --- a/src/Client/Handlers/DynamicRegistrationHandler.cs +++ b/src/Client/Handlers/DynamicRegistrationHandler.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; @@ -31,6 +32,11 @@ public DynamicRegistrationHandler() /// public string Method => "client/registerCapability"; + /// + /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// + public Type BodyType => null; + /// /// Invoke the handler. /// @@ -43,7 +49,7 @@ public DynamicRegistrationHandler() /// /// A representing the operation. /// - public Task Invoke(JObject request, CancellationToken cancellationToken) + public Task 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. diff --git a/src/Client/Handlers/IHandler.cs b/src/Client/Handlers/IHandler.cs index c206c4ab3..d893cfe7f 100644 --- a/src/Client/Handlers/IHandler.cs +++ b/src/Client/Handlers/IHandler.cs @@ -1,4 +1,6 @@ -namespace OmniSharp.Extensions.LanguageServer.Client.Handlers +using System; + +namespace OmniSharp.Extensions.LanguageServer.Client.Handlers { /// /// Represents a client-side message handler. @@ -9,5 +11,10 @@ public interface IHandler /// The name of the method handled by the handler. /// string Method { get; } + + /// + /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// + Type BodyType { get; } } } diff --git a/src/Client/Handlers/IInvokeNotificationHandler.cs b/src/Client/Handlers/IInvokeNotificationHandler.cs index e6aaedcf3..00a66de3d 100644 --- a/src/Client/Handlers/IInvokeNotificationHandler.cs +++ b/src/Client/Handlers/IInvokeNotificationHandler.cs @@ -18,6 +18,6 @@ public interface IInvokeNotificationHandler /// /// A representing the operation. /// - Task Invoke(JObject notification); + Task Invoke(object notification); } } diff --git a/src/Client/Handlers/IInvokeRequestHandler.cs b/src/Client/Handlers/IInvokeRequestHandler.cs index 480ceaa18..be7d279c4 100644 --- a/src/Client/Handlers/IInvokeRequestHandler.cs +++ b/src/Client/Handlers/IInvokeRequestHandler.cs @@ -22,6 +22,6 @@ public interface IInvokeRequestHandler /// /// A representing the operation. /// - Task Invoke(JObject request, CancellationToken cancellationToken); + Task Invoke(object request, CancellationToken cancellationToken); } } diff --git a/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs b/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs index b3c86f5eb..fe4cb757c 100644 --- a/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs +++ b/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs @@ -33,6 +33,11 @@ public JsonRpcEmptyNotificationHandler(string method, INotificationHandler handl /// public INotificationHandler Handler { get; } + /// + /// The expected CLR type of the notification body (null, since the handler does not use the request body). + /// + public override Type BodyType => null; + /// /// Invoke the handler. /// diff --git a/src/Client/Handlers/JsonRpcHandler.cs b/src/Client/Handlers/JsonRpcHandler.cs index c49d7a592..044181864 100644 --- a/src/Client/Handlers/JsonRpcHandler.cs +++ b/src/Client/Handlers/JsonRpcHandler.cs @@ -27,5 +27,10 @@ protected JsonRpcHandler(string method) /// The name of the method handled by the handler. /// public string Method { get; } + + /// + /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// + public abstract Type BodyType { get; } } } diff --git a/src/Client/Handlers/JsonRpcNotificationHandler.cs b/src/Client/Handlers/JsonRpcNotificationHandler.cs index ea83b5eaf..e2fd00302 100644 --- a/src/Client/Handlers/JsonRpcNotificationHandler.cs +++ b/src/Client/Handlers/JsonRpcNotificationHandler.cs @@ -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; @@ -39,6 +38,11 @@ public JsonRpcNotificationHandler(string method, INotificationHandler public INotificationHandler Handler { get; } + /// + /// The expected CLR type of the notification body. + /// + public override Type BodyType => typeof(TNotification); + /// /// Invoke the handler. /// @@ -48,8 +52,8 @@ public JsonRpcNotificationHandler(string method, INotificationHandler /// A representing the operation. /// - public Task Invoke(JObject notification) => Handler.Handle( - notification != null ? notification.ToObject(Serializer.Instance.JsonSerializer /* Fix me: this is ugly */) : default(TNotification) + public Task Invoke(object notification) => Handler.Handle( + (TNotification)notification ); } } From 07b6f60d23dc8b0cbed9bd4b7db6ffc8faea9f61 Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Tue, 19 Dec 2017 19:35:44 +1100 Subject: [PATCH 2/4] Propagate ISerializer from LanguageClient to child components. --- src/Client/Dispatcher/LspDispatcher.cs | 48 ++++++++++++++++++++++++-- src/Client/LanguageClient.cs | 13 ++++++- src/Client/Protocol/LspConnection.cs | 28 +++++++++------ test/Client.Tests/ClientTests.cs | 4 ++- test/Client.Tests/ConnectionTests.cs | 34 +++++++++++------- 5 files changed, 99 insertions(+), 28 deletions(-) diff --git a/src/Client/Dispatcher/LspDispatcher.cs b/src/Client/Dispatcher/LspDispatcher.cs index c4a90fc04..cc3a7b278 100644 --- a/src/Client/Dispatcher/LspDispatcher.cs +++ b/src/Client/Dispatcher/LspDispatcher.cs @@ -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 @@ -21,10 +22,22 @@ public class LspDispatcher /// /// Create a new . /// - public LspDispatcher() + /// + /// The JSON serialiser for notification / request / response payloads. + /// + public LspDispatcher(ISerializer serializer) { + if (serializer == null) + throw new ArgumentNullException(nameof(serializer)); + + Serializer = serializer; } + /// + /// The JSON serialiser to use for notification / request / response payloads. + /// + public ISerializer Serializer { get; set; } + /// /// Register a handler invoker. /// @@ -92,7 +105,9 @@ public async Task 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; } @@ -121,9 +136,36 @@ public Task 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; } + + /// + /// Deserialise a notification / request payload from JSON. + /// + /// + /// The payload's CLR type. + /// + /// + /// JSON representing the payload. + /// + /// + /// The deserialised payload (if one is present and expected). + /// + 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); + } } } diff --git a/src/Client/LanguageClient.cs b/src/Client/LanguageClient.cs index b5e9aab78..17a5b5b3c 100644 --- a/src/Client/LanguageClient.cs +++ b/src/Client/LanguageClient.cs @@ -3,6 +3,7 @@ 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; @@ -10,6 +11,7 @@ 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 @@ -23,10 +25,18 @@ namespace OmniSharp.Extensions.LanguageServer.Client public sealed class LanguageClient : IDisposable { + /// + /// The serialiser for notification / request / response bodies. + /// + /// + /// TODO: Make this injectable. And what does client version do - do we have to negotiate this? + /// + readonly ISerializer _serializer = new Serializer(ClientVersion.Lsp3); + /// /// The dispatcher for incoming requests, notifications, and responses. /// - readonly LspDispatcher _dispatcher = new LspDispatcher(); + readonly LspDispatcher _dispatcher; /// /// The handler for dynamic registration of server capabilities. @@ -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); } diff --git a/src/Client/Protocol/LspConnection.cs b/src/Client/Protocol/LspConnection.cs index 0731c6d65..b605d97a6 100644 --- a/src/Client/Protocol/LspConnection.cs +++ b/src/Client/Protocol/LspConnection.cs @@ -126,8 +126,6 @@ public sealed class LspConnection /// Task _dispatchLoop; - private readonly Serializer _serializer; - /// /// Create a new . /// @@ -160,8 +158,10 @@ public LspConnection(ILoggerFactory loggerFactory, Stream input, Stream output) Log = loggerFactory.CreateLogger(); _input = input; _output = output; - // What does client version do? Do we have to negotaite this? - _serializer = new Serializer(ClientVersion.Lsp3); + + // What does client version do? Do we have to negotiate this? + // The connection may change its Serializer instance once connected; this can be propagated to other components as required. + Serializer = new Serializer(ClientVersion.Lsp3); } /// @@ -189,6 +189,11 @@ public void Dispose() /// ILogger Log { get; } + /// + /// The JSON serializer used for notification, request, and response payloads. + /// + public Serializer Serializer { get; } + /// /// Is the connection open? /// @@ -238,6 +243,7 @@ public void Connect(LspDispatcher dispatcher) _cancellation = _cancellationSource.Token; _dispatcher = dispatcher; + _dispatcher.Serializer = Serializer; _sendLoop = SendLoop(); _receiveLoop = ReceiveLoop(); _dispatchLoop = DispatchLoop(); @@ -337,7 +343,7 @@ public void SendNotification(string method, object notification) { // No Id means it's a notification. Method = method, - Params = JObject.FromObject(notification, _serializer.JsonSerializer) + Params = JObject.FromObject(notification, Serializer.JsonSerializer) }); } @@ -395,7 +401,7 @@ public void SendNotification(string method, object notification) { Id = requestId, Method = method, - Params = request != null ? JObject.FromObject(request, _serializer.JsonSerializer) : null + Params = request != null ? JObject.FromObject(request, Serializer.JsonSerializer) : null }); await responseCompletion.Task; @@ -458,13 +464,13 @@ public void SendNotification(string method, object notification) { Id = requestId, Method = method, - Params = request != null ? JObject.FromObject(request, _serializer.JsonSerializer) : null + Params = request != null ? JObject.FromObject(request, Serializer.JsonSerializer) : null }); ServerMessage response = await responseCompletion.Task; if (response.Result != null) - return response.Result.ToObject(_serializer.JsonSerializer); + return response.Result.ToObject(Serializer.JsonSerializer); else return default(TResponse); } @@ -660,7 +666,7 @@ async Task SendMessage(TMessage message) if (message == null) throw new ArgumentNullException(nameof(message)); - string payload = JsonConvert.SerializeObject(message, _serializer.Settings); + string payload = JsonConvert.SerializeObject(message, Serializer.Settings); byte[] payloadBuffer = PayloadEncoding.GetBytes(payload); byte[] headerBuffer = HeaderEncoding.GetBytes( @@ -760,7 +766,7 @@ async Task ReceiveMessage() Log.LogDebug("Received entire payload ({ReceivedByteCount} bytes).", received); string responseBody = PayloadEncoding.GetString(requestBuffer); - ServerMessage message = JsonConvert.DeserializeObject(responseBody, _serializer.Settings); + ServerMessage message = JsonConvert.DeserializeObject(responseBody, Serializer.Settings); Log.LogDebug("Read response body {ResponseBody}.", responseBody); @@ -893,7 +899,7 @@ private void DispatchRequest(ServerMessage requestMessage) { Id = requestMessage.Id, Method = requestMessage.Method, - Result = handlerTask.Result != null ? JObject.FromObject(handlerTask.Result, _serializer.JsonSerializer) : null + Result = handlerTask.Result != null ? JObject.FromObject(handlerTask.Result, Serializer.JsonSerializer) : null }); } diff --git a/test/Client.Tests/ClientTests.cs b/test/Client.Tests/ClientTests.cs index 4e056bc6e..3778c6714 100644 --- a/test/Client.Tests/ClientTests.cs +++ b/test/Client.Tests/ClientTests.cs @@ -13,6 +13,8 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests { @@ -46,7 +48,7 @@ public ClientTests(ITestOutputHelper testOutput) /// /// The server-side dispatcher. /// - LspDispatcher ServerDispatcher { get; } = new LspDispatcher(); + LspDispatcher ServerDispatcher { get; } = new LspDispatcher(new Serializer(ClientVersion.Lsp3)); /// /// The server-side connection. diff --git a/test/Client.Tests/ConnectionTests.cs b/test/Client.Tests/ConnectionTests.cs index 4ae947823..bd9c9b923 100644 --- a/test/Client.Tests/ConnectionTests.cs +++ b/test/Client.Tests/ConnectionTests.cs @@ -4,6 +4,8 @@ using OmniSharp.Extensions.LanguageServer.Client.Protocol; using Xunit; using Xunit.Abstractions; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests { @@ -35,7 +37,7 @@ public async Task Client_HandleEmptyNotification_Success() LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); - var serverDispatcher = new LspDispatcher(); + var serverDispatcher = CreateDispatcher(); serverDispatcher.HandleEmptyNotification("test", () => { Log.LogInformation("Got notification."); @@ -44,7 +46,7 @@ public async Task Client_HandleEmptyNotification_Success() }); serverConnection.Connect(serverDispatcher); - clientConnection.Connect(new LspDispatcher()); + clientConnection.Connect(CreateDispatcher()); clientConnection.SendEmptyNotification("test"); await testCompletion.Task; @@ -66,7 +68,7 @@ public async Task Server_HandleEmptyNotification_Success() LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); - var clientDispatcher = new LspDispatcher(); + var clientDispatcher = CreateDispatcher(); clientDispatcher.HandleEmptyNotification("test", () => { Log.LogInformation("Got notification."); @@ -75,7 +77,7 @@ public async Task Server_HandleEmptyNotification_Success() }); clientConnection.Connect(clientDispatcher); - serverConnection.Connect(new LspDispatcher()); + serverConnection.Connect(CreateDispatcher()); serverConnection.SendEmptyNotification("test"); await testCompletion.Task; @@ -95,7 +97,7 @@ public async Task Server_HandleRequest_Success() LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); - var clientDispatcher = new LspDispatcher(); + var clientDispatcher = CreateDispatcher(); clientDispatcher.HandleRequest("test", (request, cancellationToken) => { Log.LogInformation("Got request: {@Request}", request); @@ -107,7 +109,7 @@ public async Task Server_HandleRequest_Success() }); clientConnection.Connect(clientDispatcher); - serverConnection.Connect(new LspDispatcher()); + serverConnection.Connect(CreateDispatcher()); TestResponse response = await serverConnection.SendRequest("test", new TestRequest { Value = 1234 @@ -132,7 +134,7 @@ public async Task Client_HandleRequest_Success() LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); - var serverDispatcher = new LspDispatcher(); + var serverDispatcher = CreateDispatcher(); serverDispatcher.HandleRequest("test", (request, cancellationToken) => { Log.LogInformation("Got request: {@Request}", request); @@ -144,7 +146,7 @@ public async Task Client_HandleRequest_Success() }); serverConnection.Connect(serverDispatcher); - clientConnection.Connect(new LspDispatcher()); + clientConnection.Connect(CreateDispatcher()); TestResponse response = await clientConnection.SendRequest("test", new TestRequest { Value = 1234 @@ -169,7 +171,7 @@ public async Task Server_HandleCommandRequest_Success() LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); - var clientDispatcher = new LspDispatcher(); + var clientDispatcher = CreateDispatcher(); clientDispatcher.HandleRequest("test", (request, cancellationToken) => { Log.LogInformation("Got request: {@Request}", request); @@ -180,7 +182,7 @@ public async Task Server_HandleCommandRequest_Success() }); clientConnection.Connect(clientDispatcher); - serverConnection.Connect(new LspDispatcher()); + serverConnection.Connect(CreateDispatcher()); await serverConnection.SendRequest("test", new TestRequest { Value = 1234 @@ -201,7 +203,7 @@ public async Task Client_HandleCommandRequest_Success() LspConnection clientConnection = await CreateClientConnection(); LspConnection serverConnection = await CreateServerConnection(); - var serverDispatcher = new LspDispatcher(); + var serverDispatcher = CreateDispatcher(); serverDispatcher.HandleRequest("test", (request, cancellationToken) => { Log.LogInformation("Got request: {@Request}", request); @@ -212,7 +214,7 @@ public async Task Client_HandleCommandRequest_Success() }); serverConnection.Connect(serverDispatcher); - clientConnection.Connect(new LspDispatcher()); + clientConnection.Connect(CreateDispatcher()); await clientConnection.SendRequest("test", new TestRequest { Value = 1234 @@ -223,5 +225,13 @@ public async Task Client_HandleCommandRequest_Success() await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected); } + + /// + /// Create an for use in tests. + /// + /// + /// The . + /// + LspDispatcher CreateDispatcher() => new LspDispatcher(new Serializer(ClientVersion.Lsp3)); } } From 1fae784128f56da85a372d30f699f51183aeefa5 Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Wed, 20 Dec 2017 09:37:30 +1100 Subject: [PATCH 3/4] Rename BodyType to PayloadType. --- src/Client/Dispatcher/LspDispatcher.cs | 4 ++-- src/Client/Handlers/DelegateEmptyNotificationHandler.cs | 4 ++-- src/Client/Handlers/DelegateHandler.cs | 4 ++-- src/Client/Handlers/DelegateNotificationHandler.cs | 4 ++-- src/Client/Handlers/DelegateRequestHandler.cs | 4 ++-- src/Client/Handlers/DelegateRequestResponseHandler.cs | 4 ++-- src/Client/Handlers/DynamicRegistrationHandler.cs | 4 ++-- src/Client/Handlers/IHandler.cs | 4 ++-- src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs | 4 ++-- src/Client/Handlers/JsonRpcHandler.cs | 4 ++-- src/Client/Handlers/JsonRpcNotificationHandler.cs | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Client/Dispatcher/LspDispatcher.cs b/src/Client/Dispatcher/LspDispatcher.cs index cc3a7b278..e2b6c546f 100644 --- a/src/Client/Dispatcher/LspDispatcher.cs +++ b/src/Client/Dispatcher/LspDispatcher.cs @@ -105,7 +105,7 @@ public async Task TryHandleNotification(string method, JObject notificatio if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeNotificationHandler notificationHandler) { - object notificationPayload = DeserializePayload(notificationHandler.BodyType, notification); + object notificationPayload = DeserializePayload(notificationHandler.PayloadType, notification); await notificationHandler.Invoke(notificationPayload); @@ -137,7 +137,7 @@ public Task TryHandleRequest(string method, JObject request, Cancellatio if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeRequestHandler requestHandler) { - object requestPayload = DeserializePayload(requestHandler.BodyType, request); + object requestPayload = DeserializePayload(requestHandler.PayloadType, request); return requestHandler.Invoke(requestPayload, cancellationToken); } diff --git a/src/Client/Handlers/DelegateEmptyNotificationHandler.cs b/src/Client/Handlers/DelegateEmptyNotificationHandler.cs index dd8cd4e1a..5b8ef95a0 100644 --- a/src/Client/Handlers/DelegateEmptyNotificationHandler.cs +++ b/src/Client/Handlers/DelegateEmptyNotificationHandler.cs @@ -33,9 +33,9 @@ public DelegateEmptyNotificationHandler(string method, NotificationHandler handl public NotificationHandler Handler { get; } /// - /// The expected CLR type of the notification body (null, since the handler does not use the request body). + /// The expected CLR type of the notification payload (null, since the handler does not use the request payload). /// - public override Type BodyType => null; + public override Type PayloadType => null; /// /// Invoke the handler. diff --git a/src/Client/Handlers/DelegateHandler.cs b/src/Client/Handlers/DelegateHandler.cs index a0bf12025..73c31a21a 100644 --- a/src/Client/Handlers/DelegateHandler.cs +++ b/src/Client/Handlers/DelegateHandler.cs @@ -28,8 +28,8 @@ protected DelegateHandler(string method) public string Method { get; } /// - /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request payload). /// - public abstract Type BodyType { get; } + public abstract Type PayloadType { get; } } } diff --git a/src/Client/Handlers/DelegateNotificationHandler.cs b/src/Client/Handlers/DelegateNotificationHandler.cs index 1a537cd96..5e5698a72 100644 --- a/src/Client/Handlers/DelegateNotificationHandler.cs +++ b/src/Client/Handlers/DelegateNotificationHandler.cs @@ -39,9 +39,9 @@ public DelegateNotificationHandler(string method, NotificationHandler Handler { get; } /// - /// The expected CLR type of the notification body. + /// The expected CLR type of the notification payload. /// - public override Type BodyType => typeof(TNotification); + public override Type PayloadType => typeof(TNotification); /// /// Invoke the handler. diff --git a/src/Client/Handlers/DelegateRequestHandler.cs b/src/Client/Handlers/DelegateRequestHandler.cs index 5f4567b60..9ae07cebc 100644 --- a/src/Client/Handlers/DelegateRequestHandler.cs +++ b/src/Client/Handlers/DelegateRequestHandler.cs @@ -40,9 +40,9 @@ public DelegateRequestHandler(string method, RequestHandler handler) public RequestHandler Handler { get; } /// - /// The expected CLR type of the request body. + /// The expected CLR type of the request payload. /// - public override Type BodyType => typeof(TRequest); + public override Type PayloadType => typeof(TRequest); /// /// Invoke the handler. diff --git a/src/Client/Handlers/DelegateRequestResponseHandler.cs b/src/Client/Handlers/DelegateRequestResponseHandler.cs index 6daea19c8..4975a253b 100644 --- a/src/Client/Handlers/DelegateRequestResponseHandler.cs +++ b/src/Client/Handlers/DelegateRequestResponseHandler.cs @@ -43,9 +43,9 @@ public DelegateRequestResponseHandler(string method, RequestHandler Handler { get; } /// - /// The expected CLR type of the request body. + /// The expected CLR type of the request payload. /// - public override Type BodyType => typeof(TRequest); + public override Type PayloadType => typeof(TRequest); /// /// Invoke the handler. diff --git a/src/Client/Handlers/DynamicRegistrationHandler.cs b/src/Client/Handlers/DynamicRegistrationHandler.cs index ec21057ea..9a371a1b4 100644 --- a/src/Client/Handlers/DynamicRegistrationHandler.cs +++ b/src/Client/Handlers/DynamicRegistrationHandler.cs @@ -33,9 +33,9 @@ public DynamicRegistrationHandler() public string Method => "client/registerCapability"; /// - /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request payload). /// - public Type BodyType => null; + public Type PayloadType => null; /// /// Invoke the handler. diff --git a/src/Client/Handlers/IHandler.cs b/src/Client/Handlers/IHandler.cs index d893cfe7f..90e523a36 100644 --- a/src/Client/Handlers/IHandler.cs +++ b/src/Client/Handlers/IHandler.cs @@ -13,8 +13,8 @@ public interface IHandler string Method { get; } /// - /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request body). /// - Type BodyType { get; } + Type PayloadType { get; } } } diff --git a/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs b/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs index fe4cb757c..94a68d378 100644 --- a/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs +++ b/src/Client/Handlers/JsonRpcEmptyNotificationHandler.cs @@ -34,9 +34,9 @@ public JsonRpcEmptyNotificationHandler(string method, INotificationHandler handl public INotificationHandler Handler { get; } /// - /// The expected CLR type of the notification body (null, since the handler does not use the request body). + /// The expected CLR type of the notification payload (null, since the handler does not use the request payload). /// - public override Type BodyType => null; + public override Type PayloadType => null; /// /// Invoke the handler. diff --git a/src/Client/Handlers/JsonRpcHandler.cs b/src/Client/Handlers/JsonRpcHandler.cs index 044181864..770d38fce 100644 --- a/src/Client/Handlers/JsonRpcHandler.cs +++ b/src/Client/Handlers/JsonRpcHandler.cs @@ -29,8 +29,8 @@ protected JsonRpcHandler(string method) public string Method { get; } /// - /// The expected CLR type of the request / notification body (if any; null if the handler does not use the request body). + /// The expected CLR type of the request / notification payload (if any; null if the handler does not use the request payload). /// - public abstract Type BodyType { get; } + public abstract Type PayloadType { get; } } } diff --git a/src/Client/Handlers/JsonRpcNotificationHandler.cs b/src/Client/Handlers/JsonRpcNotificationHandler.cs index e2fd00302..372174eb5 100644 --- a/src/Client/Handlers/JsonRpcNotificationHandler.cs +++ b/src/Client/Handlers/JsonRpcNotificationHandler.cs @@ -39,9 +39,9 @@ public JsonRpcNotificationHandler(string method, INotificationHandler Handler { get; } /// - /// The expected CLR type of the notification body. + /// The expected CLR type of the notification payload. /// - public override Type BodyType => typeof(TNotification); + public override Type PayloadType => typeof(TNotification); /// /// Invoke the handler. From 768e63f974a43484424f1840a5d40aca8e25458b Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Wed, 20 Dec 2017 09:48:39 +1100 Subject: [PATCH 4/4] Test tests for IHandler.PayloadType. --- test/Client.Tests/HandlerTests.cs | 97 +++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 test/Client.Tests/HandlerTests.cs diff --git a/test/Client.Tests/HandlerTests.cs b/test/Client.Tests/HandlerTests.cs new file mode 100644 index 000000000..f93c7a102 --- /dev/null +++ b/test/Client.Tests/HandlerTests.cs @@ -0,0 +1,97 @@ +using OmniSharp.Extensions.LanguageServer.Client.Handlers; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests +{ + /// + /// Tests for and friends. + /// + public class HandlerTests + : TestBase + { + /// + /// Create a new test suite. + /// + /// + /// Output for the current test. + /// + public HandlerTests(ITestOutputHelper testOutput) + : base(testOutput) + { + } + + /// + /// Verify that specifies the correct payload type. + /// + [Fact(DisplayName = "DelegateEmptyNotificationHandler specifies correct payload type")] + public void DelegateEmptyNotificationHandler_PayloadType() + { + IHandler handler = new DelegateEmptyNotificationHandler( + method: "test", + handler: () => + { + // Nothing to do. + } + ); + + Assert.Null(handler.PayloadType); + } + + /// + /// Verify that specifies the correct payload type. + /// + [Fact(DisplayName = "DelegateNotificationHandler specifies correct payload type")] + public void DelegateNotificationHandler_PayloadType() + { + IHandler handler = new DelegateNotificationHandler( + method: "test", + handler: notification => + { + // Nothing to do. + } + ); + + Assert.Equal(typeof(string), handler.PayloadType); + } + + /// + /// Verify that specifies the correct payload type (null). + /// + [Fact(DisplayName = "DelegateRequestHandler specifies correct payload type")] + public void DelegateRequestHandler_PayloadType() + { + IHandler handler = new DelegateRequestHandler( + method: "test", + handler: (request, cancellationToken) => + { + // Nothing to do. + + return Task.CompletedTask; + } + ); + + Assert.Equal(typeof(string), handler.PayloadType); + } + + /// + /// Verify that specifies the correct payload type (null). + /// + [Fact(DisplayName = "DelegateRequestResponseHandler specifies correct payload type")] + public void DelegateRequestResponseHandler_PayloadType() + { + IHandler handler = new DelegateRequestResponseHandler( + method: "test", + handler: (request, cancellationToken) => + { + // Nothing to do. + + return Task.FromResult("hello"); + } + ); + + Assert.Equal(typeof(string), handler.PayloadType); + } + } +}