Skip to content

Commit 07b6f60

Browse files
committed
Propagate ISerializer from LanguageClient to child components.
1 parent 64c732c commit 07b6f60

File tree

5 files changed

+99
-28
lines changed

5 files changed

+99
-28
lines changed

src/Client/Dispatcher/LspDispatcher.cs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66
using Newtonsoft.Json.Linq;
7+
using OmniSharp.Extensions.JsonRpc;
78
using OmniSharp.Extensions.LanguageServer.Client.Handlers;
89

910
namespace OmniSharp.Extensions.LanguageServer.Client.Dispatcher
@@ -21,10 +22,22 @@ public class LspDispatcher
2122
/// <summary>
2223
/// Create a new <see cref="LspDispatcher"/>.
2324
/// </summary>
24-
public LspDispatcher()
25+
/// <param name="serializer">
26+
/// The JSON serialiser for notification / request / response payloads.
27+
/// </param>
28+
public LspDispatcher(ISerializer serializer)
2529
{
30+
if (serializer == null)
31+
throw new ArgumentNullException(nameof(serializer));
32+
33+
Serializer = serializer;
2634
}
2735

36+
/// <summary>
37+
/// The JSON serialiser to use for notification / request / response payloads.
38+
/// </summary>
39+
public ISerializer Serializer { get; set; }
40+
2841
/// <summary>
2942
/// Register a handler invoker.
3043
/// </summary>
@@ -92,7 +105,9 @@ public async Task<bool> TryHandleNotification(string method, JObject notificatio
92105

93106
if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeNotificationHandler notificationHandler)
94107
{
95-
await notificationHandler.Invoke(notification);
108+
object notificationPayload = DeserializePayload(notificationHandler.BodyType, notification);
109+
110+
await notificationHandler.Invoke(notificationPayload);
96111

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

123138
if (_handlers.TryGetValue(method, out IHandler handler) && handler is IInvokeRequestHandler requestHandler)
124-
return requestHandler.Invoke(request, cancellationToken);
139+
{
140+
object requestPayload = DeserializePayload(requestHandler.BodyType, request);
141+
142+
return requestHandler.Invoke(requestPayload, cancellationToken);
143+
}
125144

126145
return null;
127146
}
147+
148+
/// <summary>
149+
/// Deserialise a notification / request payload from JSON.
150+
/// </summary>
151+
/// <param name="payloadType">
152+
/// The payload's CLR type.
153+
/// </param>
154+
/// <param name="payload">
155+
/// JSON representing the payload.
156+
/// </param>
157+
/// <returns>
158+
/// The deserialised payload (if one is present and expected).
159+
/// </returns>
160+
object DeserializePayload(Type payloadType, JObject payload)
161+
{
162+
if (payloadType == null)
163+
throw new ArgumentNullException(nameof(payloadType));
164+
165+
if (payloadType == null || payload == null)
166+
return null;
167+
168+
return payload.ToObject(payloadType, Serializer.JsonSerializer);
169+
}
128170
}
129171
}

src/Client/LanguageClient.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
using System.Threading;
44
using System.Threading.Tasks;
55
using Microsoft.Extensions.Logging;
6+
using OmniSharp.Extensions.JsonRpc;
67
using OmniSharp.Extensions.LanguageServer.Client.Clients;
78
using OmniSharp.Extensions.LanguageServer.Client.Dispatcher;
89
using OmniSharp.Extensions.LanguageServer.Client.Handlers;
910
using OmniSharp.Extensions.LanguageServer.Client.Processes;
1011
using OmniSharp.Extensions.LanguageServer.Client.Protocol;
1112
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
1213
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
14+
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
1315
using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities;
1416

1517
namespace OmniSharp.Extensions.LanguageServer.Client
@@ -23,10 +25,18 @@ namespace OmniSharp.Extensions.LanguageServer.Client
2325
public sealed class LanguageClient
2426
: IDisposable
2527
{
28+
/// <summary>
29+
/// The serialiser for notification / request / response bodies.
30+
/// </summary>
31+
/// <remarks>
32+
/// TODO: Make this injectable. And what does client version do - do we have to negotiate this?
33+
/// </remarks>
34+
readonly ISerializer _serializer = new Serializer(ClientVersion.Lsp3);
35+
2636
/// <summary>
2737
/// The dispatcher for incoming requests, notifications, and responses.
2838
/// </summary>
29-
readonly LspDispatcher _dispatcher = new LspDispatcher();
39+
readonly LspDispatcher _dispatcher;
3040

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

114+
_dispatcher = new LspDispatcher(_serializer);
104115
_dispatcher.RegisterHandler(_dynamicRegistrationHandler);
105116
}
106117

src/Client/Protocol/LspConnection.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,6 @@ public sealed class LspConnection
126126
/// </summary>
127127
Task _dispatchLoop;
128128

129-
private readonly Serializer _serializer;
130-
131129
/// <summary>
132130
/// Create a new <see cref="LspConnection"/>.
133131
/// </summary>
@@ -160,8 +158,10 @@ public LspConnection(ILoggerFactory loggerFactory, Stream input, Stream output)
160158
Log = loggerFactory.CreateLogger<LspConnection>();
161159
_input = input;
162160
_output = output;
163-
// What does client version do? Do we have to negotaite this?
164-
_serializer = new Serializer(ClientVersion.Lsp3);
161+
162+
// What does client version do? Do we have to negotiate this?
163+
// The connection may change its Serializer instance once connected; this can be propagated to other components as required.
164+
Serializer = new Serializer(ClientVersion.Lsp3);
165165
}
166166

167167
/// <summary>
@@ -189,6 +189,11 @@ public void Dispose()
189189
/// </summary>
190190
ILogger Log { get; }
191191

192+
/// <summary>
193+
/// The JSON serializer used for notification, request, and response payloads.
194+
/// </summary>
195+
public Serializer Serializer { get; }
196+
192197
/// <summary>
193198
/// Is the connection open?
194199
/// </summary>
@@ -238,6 +243,7 @@ public void Connect(LspDispatcher dispatcher)
238243
_cancellation = _cancellationSource.Token;
239244

240245
_dispatcher = dispatcher;
246+
_dispatcher.Serializer = Serializer;
241247
_sendLoop = SendLoop();
242248
_receiveLoop = ReceiveLoop();
243249
_dispatchLoop = DispatchLoop();
@@ -337,7 +343,7 @@ public void SendNotification(string method, object notification)
337343
{
338344
// No Id means it's a notification.
339345
Method = method,
340-
Params = JObject.FromObject(notification, _serializer.JsonSerializer)
346+
Params = JObject.FromObject(notification, Serializer.JsonSerializer)
341347
});
342348
}
343349

@@ -395,7 +401,7 @@ public void SendNotification(string method, object notification)
395401
{
396402
Id = requestId,
397403
Method = method,
398-
Params = request != null ? JObject.FromObject(request, _serializer.JsonSerializer) : null
404+
Params = request != null ? JObject.FromObject(request, Serializer.JsonSerializer) : null
399405
});
400406

401407
await responseCompletion.Task;
@@ -458,13 +464,13 @@ public void SendNotification(string method, object notification)
458464
{
459465
Id = requestId,
460466
Method = method,
461-
Params = request != null ? JObject.FromObject(request, _serializer.JsonSerializer) : null
467+
Params = request != null ? JObject.FromObject(request, Serializer.JsonSerializer) : null
462468
});
463469

464470
ServerMessage response = await responseCompletion.Task;
465471

466472
if (response.Result != null)
467-
return response.Result.ToObject<TResponse>(_serializer.JsonSerializer);
473+
return response.Result.ToObject<TResponse>(Serializer.JsonSerializer);
468474
else
469475
return default(TResponse);
470476
}
@@ -660,7 +666,7 @@ async Task SendMessage<TMessage>(TMessage message)
660666
if (message == null)
661667
throw new ArgumentNullException(nameof(message));
662668

663-
string payload = JsonConvert.SerializeObject(message, _serializer.Settings);
669+
string payload = JsonConvert.SerializeObject(message, Serializer.Settings);
664670
byte[] payloadBuffer = PayloadEncoding.GetBytes(payload);
665671

666672
byte[] headerBuffer = HeaderEncoding.GetBytes(
@@ -760,7 +766,7 @@ async Task<ServerMessage> ReceiveMessage()
760766
Log.LogDebug("Received entire payload ({ReceivedByteCount} bytes).", received);
761767

762768
string responseBody = PayloadEncoding.GetString(requestBuffer);
763-
ServerMessage message = JsonConvert.DeserializeObject<ServerMessage>(responseBody, _serializer.Settings);
769+
ServerMessage message = JsonConvert.DeserializeObject<ServerMessage>(responseBody, Serializer.Settings);
764770

765771
Log.LogDebug("Read response body {ResponseBody}.", responseBody);
766772

@@ -893,7 +899,7 @@ private void DispatchRequest(ServerMessage requestMessage)
893899
{
894900
Id = requestMessage.Id,
895901
Method = requestMessage.Method,
896-
Result = handlerTask.Result != null ? JObject.FromObject(handlerTask.Result, _serializer.JsonSerializer) : null
902+
Result = handlerTask.Result != null ? JObject.FromObject(handlerTask.Result, Serializer.JsonSerializer) : null
897903
});
898904
}
899905

test/Client.Tests/ClientTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1414
using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities;
1515
using OmniSharp.Extensions.LanguageServer.Protocol;
16+
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
17+
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
1618

1719
namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests
1820
{
@@ -46,7 +48,7 @@ public ClientTests(ITestOutputHelper testOutput)
4648
/// <summary>
4749
/// The server-side dispatcher.
4850
/// </summary>
49-
LspDispatcher ServerDispatcher { get; } = new LspDispatcher();
51+
LspDispatcher ServerDispatcher { get; } = new LspDispatcher(new Serializer(ClientVersion.Lsp3));
5052

5153
/// <summary>
5254
/// The server-side connection.

test/Client.Tests/ConnectionTests.cs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using OmniSharp.Extensions.LanguageServer.Client.Protocol;
55
using Xunit;
66
using Xunit.Abstractions;
7+
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
8+
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
79

810
namespace OmniSharp.Extensions.LanguageServerProtocol.Client.Tests
911
{
@@ -35,7 +37,7 @@ public async Task Client_HandleEmptyNotification_Success()
3537
LspConnection clientConnection = await CreateClientConnection();
3638
LspConnection serverConnection = await CreateServerConnection();
3739

38-
var serverDispatcher = new LspDispatcher();
40+
var serverDispatcher = CreateDispatcher();
3941
serverDispatcher.HandleEmptyNotification("test", () =>
4042
{
4143
Log.LogInformation("Got notification.");
@@ -44,7 +46,7 @@ public async Task Client_HandleEmptyNotification_Success()
4446
});
4547
serverConnection.Connect(serverDispatcher);
4648

47-
clientConnection.Connect(new LspDispatcher());
49+
clientConnection.Connect(CreateDispatcher());
4850
clientConnection.SendEmptyNotification("test");
4951

5052
await testCompletion.Task;
@@ -66,7 +68,7 @@ public async Task Server_HandleEmptyNotification_Success()
6668
LspConnection clientConnection = await CreateClientConnection();
6769
LspConnection serverConnection = await CreateServerConnection();
6870

69-
var clientDispatcher = new LspDispatcher();
71+
var clientDispatcher = CreateDispatcher();
7072
clientDispatcher.HandleEmptyNotification("test", () =>
7173
{
7274
Log.LogInformation("Got notification.");
@@ -75,7 +77,7 @@ public async Task Server_HandleEmptyNotification_Success()
7577
});
7678
clientConnection.Connect(clientDispatcher);
7779

78-
serverConnection.Connect(new LspDispatcher());
80+
serverConnection.Connect(CreateDispatcher());
7981
serverConnection.SendEmptyNotification("test");
8082

8183
await testCompletion.Task;
@@ -95,7 +97,7 @@ public async Task Server_HandleRequest_Success()
9597
LspConnection clientConnection = await CreateClientConnection();
9698
LspConnection serverConnection = await CreateServerConnection();
9799

98-
var clientDispatcher = new LspDispatcher();
100+
var clientDispatcher = CreateDispatcher();
99101
clientDispatcher.HandleRequest<TestRequest, TestResponse>("test", (request, cancellationToken) =>
100102
{
101103
Log.LogInformation("Got request: {@Request}", request);
@@ -107,7 +109,7 @@ public async Task Server_HandleRequest_Success()
107109
});
108110
clientConnection.Connect(clientDispatcher);
109111

110-
serverConnection.Connect(new LspDispatcher());
112+
serverConnection.Connect(CreateDispatcher());
111113
TestResponse response = await serverConnection.SendRequest<TestResponse>("test", new TestRequest
112114
{
113115
Value = 1234
@@ -132,7 +134,7 @@ public async Task Client_HandleRequest_Success()
132134
LspConnection clientConnection = await CreateClientConnection();
133135
LspConnection serverConnection = await CreateServerConnection();
134136

135-
var serverDispatcher = new LspDispatcher();
137+
var serverDispatcher = CreateDispatcher();
136138
serverDispatcher.HandleRequest<TestRequest, TestResponse>("test", (request, cancellationToken) =>
137139
{
138140
Log.LogInformation("Got request: {@Request}", request);
@@ -144,7 +146,7 @@ public async Task Client_HandleRequest_Success()
144146
});
145147
serverConnection.Connect(serverDispatcher);
146148

147-
clientConnection.Connect(new LspDispatcher());
149+
clientConnection.Connect(CreateDispatcher());
148150
TestResponse response = await clientConnection.SendRequest<TestResponse>("test", new TestRequest
149151
{
150152
Value = 1234
@@ -169,7 +171,7 @@ public async Task Server_HandleCommandRequest_Success()
169171
LspConnection clientConnection = await CreateClientConnection();
170172
LspConnection serverConnection = await CreateServerConnection();
171173

172-
var clientDispatcher = new LspDispatcher();
174+
var clientDispatcher = CreateDispatcher();
173175
clientDispatcher.HandleRequest<TestRequest>("test", (request, cancellationToken) =>
174176
{
175177
Log.LogInformation("Got request: {@Request}", request);
@@ -180,7 +182,7 @@ public async Task Server_HandleCommandRequest_Success()
180182
});
181183
clientConnection.Connect(clientDispatcher);
182184

183-
serverConnection.Connect(new LspDispatcher());
185+
serverConnection.Connect(CreateDispatcher());
184186
await serverConnection.SendRequest("test", new TestRequest
185187
{
186188
Value = 1234
@@ -201,7 +203,7 @@ public async Task Client_HandleCommandRequest_Success()
201203
LspConnection clientConnection = await CreateClientConnection();
202204
LspConnection serverConnection = await CreateServerConnection();
203205

204-
var serverDispatcher = new LspDispatcher();
206+
var serverDispatcher = CreateDispatcher();
205207
serverDispatcher.HandleRequest<TestRequest>("test", (request, cancellationToken) =>
206208
{
207209
Log.LogInformation("Got request: {@Request}", request);
@@ -212,7 +214,7 @@ public async Task Client_HandleCommandRequest_Success()
212214
});
213215
serverConnection.Connect(serverDispatcher);
214216

215-
clientConnection.Connect(new LspDispatcher());
217+
clientConnection.Connect(CreateDispatcher());
216218
await clientConnection.SendRequest("test", new TestRequest
217219
{
218220
Value = 1234
@@ -223,5 +225,13 @@ public async Task Client_HandleCommandRequest_Success()
223225

224226
await Task.WhenAll(clientConnection.HasHasDisconnected, serverConnection.HasHasDisconnected);
225227
}
228+
229+
/// <summary>
230+
/// Create an <see cref="LspDispatcher"/> for use in tests.
231+
/// </summary>
232+
/// <returns>
233+
/// The <see cref="LspDispatcher"/>.
234+
/// </returns>
235+
LspDispatcher CreateDispatcher() => new LspDispatcher(new Serializer(ClientVersion.Lsp3));
226236
}
227237
}

0 commit comments

Comments
 (0)