From 9869b91fa0f5df7e6e2c7b85554f26c7f1043b2d Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 19 Oct 2017 11:23:50 -0400 Subject: [PATCH 1/2] Fixed error message not being formatted correctly --- src/JsonRpc/Client/Response.cs | 17 +++++-- src/JsonRpc/ErrorResponse.cs | 10 ++-- src/JsonRpc/InputHandler.cs | 5 +- src/JsonRpc/Reciever.cs | 5 +- src/JsonRpc/{Error.cs => RpcError.cs} | 22 ++++---- src/JsonRpc/RpcErrorConverter.cs | 51 +++++++++++++++++++ src/JsonRpc/Server/Messages/ErrorMessage.cs | 18 ++++++- src/JsonRpc/Server/Messages/InternalError.cs | 4 +- src/JsonRpc/Server/Messages/InvalidParams.cs | 4 +- src/JsonRpc/Server/Messages/InvalidRequest.cs | 4 +- src/JsonRpc/Server/Messages/MethodNotFound.cs | 4 +- src/JsonRpc/Server/Messages/ParseError.cs | 4 +- src/JsonRpc/Server/Renor.cs | 10 ++-- src/JsonRpc/Server/Response.cs | 12 +++-- src/Lsp/Messages/RequestCancelled.cs | 4 +- src/Lsp/Messages/ServerErrorEnd.cs | 4 +- src/Lsp/Messages/ServerErrorStart.cs | 2 +- src/Lsp/Messages/ServerNotInitialized.cs | 4 +- src/Lsp/Messages/UnknownErrorCode.cs | 4 +- test/JsonRpc.Tests/InputHandlerTests.cs | 4 +- test/JsonRpc.Tests/OutputHandlerTests.cs | 8 +-- .../MediatorTestsRequestHandlerOfTRequest.cs | 4 +- .../Lsp.Tests/Messages/ServerErrorEndTests.cs | 3 +- .../ServerErrorEndTests_$SimpleTest.json | 6 +-- .../Messages/ServerErrorStartTests.cs | 4 +- .../ServerErrorStartTests_$SimpleTest.json | 6 +-- .../Messages/ServerNotInitializedTests.cs | 3 +- ...ServerNotInitializedTests_$SimpleTest.json | 6 +-- .../Messages/UnknownErrorCodeTests.cs | 3 +- .../UnknownErrorCodeTests_$SimpleTest.json | 6 +-- 30 files changed, 163 insertions(+), 78 deletions(-) rename src/JsonRpc/{Error.cs => RpcError.cs} (51%) create mode 100644 src/JsonRpc/RpcErrorConverter.cs diff --git a/src/JsonRpc/Client/Response.cs b/src/JsonRpc/Client/Response.cs index c7ddb3fd1..8a672a2ee 100644 --- a/src/JsonRpc/Client/Response.cs +++ b/src/JsonRpc/Client/Response.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace OmniSharp.Extensions.JsonRpc.Client @@ -6,8 +6,9 @@ namespace OmniSharp.Extensions.JsonRpc.Client [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public class Response { - public Response(object id) : this(id, null) + public Response(object id) { + Id = id; } public Response(object id, object result) @@ -16,10 +17,20 @@ public Response(object id, object result) Result = result; } + public Response(object id, RpcError result) + { + Id = id; + Result = result; + } + public string ProtocolVersion { get; set; } = "2.0"; public object Id { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public object Result { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public RpcError Error { get; set; } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/ErrorResponse.cs b/src/JsonRpc/ErrorResponse.cs index c29eba17d..27fb83178 100644 --- a/src/JsonRpc/ErrorResponse.cs +++ b/src/JsonRpc/ErrorResponse.cs @@ -1,10 +1,10 @@ -using OmniSharp.Extensions.JsonRpc.Client; +using OmniSharp.Extensions.JsonRpc.Client; namespace OmniSharp.Extensions.JsonRpc { public struct ErrorResponse { - public ErrorResponse(Error error) + public ErrorResponse(RpcError error) { Response = null; Error = error; @@ -20,7 +20,7 @@ public ErrorResponse(Response response) public Response Response { get; } public bool IsError => Error != null; - public Error Error { get; } + public RpcError Error { get; } public object Value => IsResponse ? (object)Response : IsError ? Error : null; public static implicit operator ErrorResponse(Response response) @@ -28,9 +28,9 @@ public static implicit operator ErrorResponse(Response response) return new ErrorResponse(response); } - public static implicit operator ErrorResponse(Error error) + public static implicit operator ErrorResponse(RpcError error) { return new ErrorResponse(error); } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/InputHandler.cs b/src/JsonRpc/InputHandler.cs index cb8231deb..d20566eef 100644 --- a/src/JsonRpc/InputHandler.cs +++ b/src/JsonRpc/InputHandler.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using OmniSharp.Extensions.JsonRpc.Server.Messages; namespace OmniSharp.Extensions.JsonRpc @@ -153,7 +154,7 @@ private void HandleRequest(string request) } else { - tcs.SetException(new Exception(response.Error)); + tcs.SetException(new Exception(JsonConvert.SerializeObject(response.Error))); } } diff --git a/src/JsonRpc/Reciever.cs b/src/JsonRpc/Reciever.cs index eef9fa7be..72d233c19 100644 --- a/src/JsonRpc/Reciever.cs +++ b/src/JsonRpc/Reciever.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc.Server; @@ -68,7 +68,8 @@ protected virtual Renor GetRenor(JToken @object) if (hasRequestId && request.TryGetValue("error", out var errorResponse)) { - return new Response(requestId, errorResponse.ToString()); + // TODO: this doesn't seem right. + return new RpcError(requestId, new ErrorMessage(-1337, "Unknown error response", errorResponse)); } var method = request["method"]?.Value(); diff --git a/src/JsonRpc/Error.cs b/src/JsonRpc/RpcError.cs similarity index 51% rename from src/JsonRpc/Error.cs rename to src/JsonRpc/RpcError.cs index c76ae8ab1..ba7ddb479 100644 --- a/src/JsonRpc/Error.cs +++ b/src/JsonRpc/RpcError.cs @@ -1,22 +1,22 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using OmniSharp.Extensions.JsonRpc.Server.Messages; namespace OmniSharp.Extensions.JsonRpc { - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public class Error + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy)), JsonConverter(typeof(RpcErrorConverter))] + public class RpcError { - public Error(object id, ErrorMessage message) : this(id, message, "2.0") + public RpcError(object id, ErrorMessage message) : this(id, message, "2.0") { } [JsonConstructor] - public Error(object id, ErrorMessage message, string protocolVersion) + public RpcError(object id, ErrorMessage message, string protocolVersion) { Id = id; - Message = message; + Error = message; ProtocolVersion = protocolVersion; } @@ -26,18 +26,18 @@ public Error(object id, ErrorMessage message, string protocolVersion) public object Id { get; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public ErrorMessage Message { get; } + public ErrorMessage Error { get; } } - public class Error : Error + public class RpcError : RpcError { - public Error(object id, ErrorMessage message) : this(id, message, "2.0") + public RpcError(object id, ErrorMessage message) : this(id, message, "2.0") { } [JsonConstructor] - public Error(object id, ErrorMessage message, string protocolVersion) : base(id, message, protocolVersion) + public RpcError(object id, ErrorMessage message, string protocolVersion) : base(id, message, protocolVersion) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/RpcErrorConverter.cs b/src/JsonRpc/RpcErrorConverter.cs new file mode 100644 index 000000000..e72d1c73e --- /dev/null +++ b/src/JsonRpc/RpcErrorConverter.cs @@ -0,0 +1,51 @@ +using System; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc.Server.Messages; + +namespace OmniSharp.Extensions.JsonRpc +{ + public class RpcErrorConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JObject.Load(reader); + + var messageDataType = objectType == typeof(RpcError) + ? typeof(object) + : objectType.GetTypeInfo().GetGenericArguments()[0]; + + object requestId = null; + if (obj.TryGetValue("id", out var id)) + { + var idString = id.Type == JTokenType.String ? (string)id : null; + var idLong = id.Type == JTokenType.Integer ? (long?)id : null; + requestId = idString ?? (idLong.HasValue ? (object)idLong.Value : null); + } + + object data = null; + if (obj.TryGetValue("error", out var dataToken)) + { + var errorMessageType = typeof(ErrorMessage<>).MakeGenericType(messageDataType); + data = dataToken.ToObject(errorMessageType); + } + + return Activator.CreateInstance(objectType, requestId, data, obj["protocolVersion"].ToString()); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(RpcError) || + (objectType.GetTypeInfo().IsGenericType && objectType.GetTypeInfo().GetGenericTypeDefinition() == typeof(RpcError<>)); + } + + public override bool CanWrite { get; } = false; + public override bool CanRead { get; } = true; + } +} \ No newline at end of file diff --git a/src/JsonRpc/Server/Messages/ErrorMessage.cs b/src/JsonRpc/Server/Messages/ErrorMessage.cs index c474ade61..324c2d8c6 100644 --- a/src/JsonRpc/Server/Messages/ErrorMessage.cs +++ b/src/JsonRpc/Server/Messages/ErrorMessage.cs @@ -3,20 +3,32 @@ namespace OmniSharp.Extensions.JsonRpc.Server.Messages { + public interface IErrorMessage + { + int Code { get; } + + string Message { get; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + object Data { get; } + } + public class ErrorMessage: ErrorMessage { public ErrorMessage(int code, string message) : base(code, message, null) { } + [JsonConstructor] public ErrorMessage(int code, string message, object data) : base(code, message, data) { } } [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public class ErrorMessage + public class ErrorMessage : IErrorMessage { + [JsonConstructor] public ErrorMessage(int code, string message, T data) { Code = code; @@ -30,5 +42,7 @@ public ErrorMessage(int code, string message, T data) [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public T Data { get; } + + object IErrorMessage.Data => Data; } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Messages/InternalError.cs b/src/JsonRpc/Server/Messages/InternalError.cs index d139a8be6..304a69462 100644 --- a/src/JsonRpc/Server/Messages/InternalError.cs +++ b/src/JsonRpc/Server/Messages/InternalError.cs @@ -1,8 +1,8 @@ namespace OmniSharp.Extensions.JsonRpc.Server.Messages { - public class InternalError : Error + public class InternalError : RpcError { public InternalError() : this(null) { } public InternalError(object id) : base(id, new ErrorMessage(-32602, "Internal Error")) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Messages/InvalidParams.cs b/src/JsonRpc/Server/Messages/InvalidParams.cs index f37820d3b..be3f74427 100644 --- a/src/JsonRpc/Server/Messages/InvalidParams.cs +++ b/src/JsonRpc/Server/Messages/InvalidParams.cs @@ -1,8 +1,8 @@ namespace OmniSharp.Extensions.JsonRpc.Server.Messages { - public class InvalidParams : Error + public class InvalidParams : RpcError { public InvalidParams() : this(null) { } public InvalidParams(object id) : base(id, new ErrorMessage(-32602, "Invalid params")) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Messages/InvalidRequest.cs b/src/JsonRpc/Server/Messages/InvalidRequest.cs index b78dc8e69..58977436e 100644 --- a/src/JsonRpc/Server/Messages/InvalidRequest.cs +++ b/src/JsonRpc/Server/Messages/InvalidRequest.cs @@ -1,6 +1,6 @@ namespace OmniSharp.Extensions.JsonRpc.Server.Messages { - public class InvalidRequest : Error + public class InvalidRequest : RpcError { public InvalidRequest() : base(null, new ErrorMessage(-32600, $"Invalid Request")) { } @@ -8,4 +8,4 @@ public class InvalidRequest : Error public InvalidRequest(string message) : base(null, new ErrorMessage(-32600, $"Invalid Request - {message}")) { } public InvalidRequest(object id, string message) : base(id, new ErrorMessage(-32600, $"Invalid Request - {message}")) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Messages/MethodNotFound.cs b/src/JsonRpc/Server/Messages/MethodNotFound.cs index fedd30141..bc1c37f24 100644 --- a/src/JsonRpc/Server/Messages/MethodNotFound.cs +++ b/src/JsonRpc/Server/Messages/MethodNotFound.cs @@ -1,7 +1,7 @@ namespace OmniSharp.Extensions.JsonRpc.Server.Messages { - public class MethodNotFound : Error + public class MethodNotFound : RpcError { public MethodNotFound(object id) : base(id, new ErrorMessage(-32601, "Method not found")) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Messages/ParseError.cs b/src/JsonRpc/Server/Messages/ParseError.cs index 084d6c612..dfe80e65a 100644 --- a/src/JsonRpc/Server/Messages/ParseError.cs +++ b/src/JsonRpc/Server/Messages/ParseError.cs @@ -1,8 +1,8 @@ namespace OmniSharp.Extensions.JsonRpc.Server.Messages { - public class ParseError : Error + public class ParseError : RpcError { public ParseError() : this(null) { } public ParseError(object id) : base(id, new ErrorMessage(-32700, "Parse Error")) { } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Renor.cs b/src/JsonRpc/Server/Renor.cs index e7361e15c..deab1a5a0 100644 --- a/src/JsonRpc/Server/Renor.cs +++ b/src/JsonRpc/Server/Renor.cs @@ -1,4 +1,4 @@ -namespace OmniSharp.Extensions.JsonRpc.Server +namespace OmniSharp.Extensions.JsonRpc.Server { /// /// Request, Error, Notification or Response @@ -22,7 +22,7 @@ internal Renor(Request request) Response = null; } - internal Renor(Error errorMessage) + internal Renor(RpcError errorMessage) { Notification = null; Request = null; @@ -45,7 +45,7 @@ internal Renor(Response response) public Request Request { get; } public bool IsError => Error != null; - public Error Error { get; } + public RpcError Error { get; } public bool IsResponse => Response != null; public Response Response { get; } @@ -60,7 +60,7 @@ public static implicit operator Renor(Request request) return new Renor(request); } - public static implicit operator Renor(Error error) + public static implicit operator Renor(RpcError error) { return new Renor(error); } @@ -70,4 +70,4 @@ public static implicit operator Renor(Response response) return new Renor(response); } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/Server/Response.cs b/src/JsonRpc/Server/Response.cs index 86cd10b08..f29af46fb 100644 --- a/src/JsonRpc/Server/Response.cs +++ b/src/JsonRpc/Server/Response.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc.Server.Messages; namespace OmniSharp.Extensions.JsonRpc.Server { @@ -10,7 +12,7 @@ public Response(object id, JToken result) Result = result; } - public Response(object id, string error) + public Response(object id, IErrorMessage error) { Id = id; Error = error; @@ -20,8 +22,10 @@ public Response(object id, string error) public object Id { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public JToken Result { get; set; } - public string Error { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public IErrorMessage Error { get; set; } } -} \ No newline at end of file +} diff --git a/src/Lsp/Messages/RequestCancelled.cs b/src/Lsp/Messages/RequestCancelled.cs index 26cb694ae..0c8a6a0e6 100644 --- a/src/Lsp/Messages/RequestCancelled.cs +++ b/src/Lsp/Messages/RequestCancelled.cs @@ -3,8 +3,8 @@ namespace OmniSharp.Extensions.LanguageServer.Messages { - public class RequestCancelled : Error + public class RequestCancelled : RpcError { internal RequestCancelled() : base(null, new ErrorMessage(-32800, "Request Cancelled")) { } } -} \ No newline at end of file +} diff --git a/src/Lsp/Messages/ServerErrorEnd.cs b/src/Lsp/Messages/ServerErrorEnd.cs index e354c5c14..be29483ff 100644 --- a/src/Lsp/Messages/ServerErrorEnd.cs +++ b/src/Lsp/Messages/ServerErrorEnd.cs @@ -3,8 +3,8 @@ namespace OmniSharp.Extensions.LanguageServer.Messages { - public class ServerErrorEnd : Error + public class ServerErrorEnd : RpcError { internal ServerErrorEnd() : base(null, new ErrorMessage(-32000, "Server Error End")) { } } -} \ No newline at end of file +} diff --git a/src/Lsp/Messages/ServerErrorStart.cs b/src/Lsp/Messages/ServerErrorStart.cs index 611f20b65..7c6759db9 100644 --- a/src/Lsp/Messages/ServerErrorStart.cs +++ b/src/Lsp/Messages/ServerErrorStart.cs @@ -3,7 +3,7 @@ namespace OmniSharp.Extensions.LanguageServer.Messages { - public class ServerErrorStart : Error + public class ServerErrorStart : RpcError { internal ServerErrorStart(object data) : base(null, new ErrorMessage(-32099, "Server Error Start", data)) { } } diff --git a/src/Lsp/Messages/ServerNotInitialized.cs b/src/Lsp/Messages/ServerNotInitialized.cs index dadc3357d..68c961c61 100644 --- a/src/Lsp/Messages/ServerNotInitialized.cs +++ b/src/Lsp/Messages/ServerNotInitialized.cs @@ -3,8 +3,8 @@ namespace OmniSharp.Extensions.LanguageServer.Messages { - public class ServerNotInitialized : Error + public class ServerNotInitialized : RpcError { internal ServerNotInitialized() : base(null, new ErrorMessage(-32002, "Server Not Initialized")) { } } -} \ No newline at end of file +} diff --git a/src/Lsp/Messages/UnknownErrorCode.cs b/src/Lsp/Messages/UnknownErrorCode.cs index 4d3029641..8dcd55d31 100644 --- a/src/Lsp/Messages/UnknownErrorCode.cs +++ b/src/Lsp/Messages/UnknownErrorCode.cs @@ -3,8 +3,8 @@ namespace OmniSharp.Extensions.LanguageServer.Messages { - public class UnknownErrorCode : Error + public class UnknownErrorCode : RpcError { internal UnknownErrorCode() : base(null, new ErrorMessage(-32602, "Unknown Error Code")) { } } -} \ No newline at end of file +} diff --git a/test/JsonRpc.Tests/InputHandlerTests.cs b/test/JsonRpc.Tests/InputHandlerTests.cs index d4a189e43..53867c1b3 100644 --- a/test/JsonRpc.Tests/InputHandlerTests.cs +++ b/test/JsonRpc.Tests/InputHandlerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -179,7 +179,7 @@ public void ShouldHandleError() var reciever = Substitute.For(); var incomingRequestRouter = Substitute.For(); - var error = new Error(1, new ErrorMessage(1, "abc")); + var error = new RpcError(1, new ErrorMessage(1, "abc")); reciever.IsValid(Arg.Any()).Returns(true); reciever.GetRequests(Arg.Any()) .Returns(c => (new Renor[] { error }, false)); diff --git a/test/JsonRpc.Tests/OutputHandlerTests.cs b/test/JsonRpc.Tests/OutputHandlerTests.cs index aa4bc28d3..2760d6dae 100644 --- a/test/JsonRpc.Tests/OutputHandlerTests.cs +++ b/test/JsonRpc.Tests/OutputHandlerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -41,18 +41,18 @@ public async Task ShouldSerializeValues() cts.Cancel(); }); }); - var value = new Response(1); + var value = new Response(1, 1); using (handler) { handler.Send(value); await wait(); - const string send = "Content-Length: 46\r\n\r\n{\"protocolVersion\":\"2.0\",\"id\":1,\"result\":null}"; + const string send = "Content-Length: 43\r\n\r\n{\"protocolVersion\":\"2.0\",\"id\":1,\"result\":1}"; received.Should().Be(send); var b = System.Text.Encoding.UTF8.GetBytes(send); w.Received().Write(Arg.Any(), 0, b.Length); // can't compare b here, because it is only value-equal and this test tests reference equality } } } -} \ No newline at end of file +} diff --git a/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs b/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs index 930a09009..8033d5e7f 100644 --- a/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs +++ b/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -47,4 +47,4 @@ public async Task RequestsCancellation() } } -} \ No newline at end of file +} diff --git a/test/Lsp.Tests/Messages/ServerErrorEndTests.cs b/test/Lsp.Tests/Messages/ServerErrorEndTests.cs index 434c8c014..75faba8a2 100644 --- a/test/Lsp.Tests/Messages/ServerErrorEndTests.cs +++ b/test/Lsp.Tests/Messages/ServerErrorEndTests.cs @@ -1,6 +1,7 @@ using System; using FluentAssertions; using Newtonsoft.Json; +using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Messages; using Xunit; @@ -16,7 +17,7 @@ public void SimpleTest(string expected) result.Should().Be(expected); - var deresult = JsonConvert.DeserializeObject(expected); + var deresult = JsonConvert.DeserializeObject(expected); deresult.ShouldBeEquivalentTo(model); } } diff --git a/test/Lsp.Tests/Messages/ServerErrorEndTests_$SimpleTest.json b/test/Lsp.Tests/Messages/ServerErrorEndTests_$SimpleTest.json index 04284c31b..c41806785 100644 --- a/test/Lsp.Tests/Messages/ServerErrorEndTests_$SimpleTest.json +++ b/test/Lsp.Tests/Messages/ServerErrorEndTests_$SimpleTest.json @@ -1,7 +1,7 @@ -{ +{ "protocolVersion": "2.0", - "message": { + "error": { "code": -32000, "message": "Server Error End" } -} \ No newline at end of file +} diff --git a/test/Lsp.Tests/Messages/ServerErrorStartTests.cs b/test/Lsp.Tests/Messages/ServerErrorStartTests.cs index 539ee4c1b..dbd584437 100644 --- a/test/Lsp.Tests/Messages/ServerErrorStartTests.cs +++ b/test/Lsp.Tests/Messages/ServerErrorStartTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using FluentAssertions; using Newtonsoft.Json; using OmniSharp.Extensions.JsonRpc; @@ -17,7 +17,7 @@ public void SimpleTest(string expected) result.Should().Be(expected); - var deresult = JsonConvert.DeserializeObject(expected); + var deresult = JsonConvert.DeserializeObject(expected); deresult.ShouldBeEquivalentTo(model); } } diff --git a/test/Lsp.Tests/Messages/ServerErrorStartTests_$SimpleTest.json b/test/Lsp.Tests/Messages/ServerErrorStartTests_$SimpleTest.json index a96ee3987..55b32e511 100644 --- a/test/Lsp.Tests/Messages/ServerErrorStartTests_$SimpleTest.json +++ b/test/Lsp.Tests/Messages/ServerErrorStartTests_$SimpleTest.json @@ -1,8 +1,8 @@ -{ +{ "protocolVersion": "2.0", - "message": { + "error": { "code": -32099, "message": "Server Error Start", "data": "abcd" } -} \ No newline at end of file +} diff --git a/test/Lsp.Tests/Messages/ServerNotInitializedTests.cs b/test/Lsp.Tests/Messages/ServerNotInitializedTests.cs index 55944b7c8..df8ad9559 100644 --- a/test/Lsp.Tests/Messages/ServerNotInitializedTests.cs +++ b/test/Lsp.Tests/Messages/ServerNotInitializedTests.cs @@ -1,6 +1,7 @@ using System; using FluentAssertions; using Newtonsoft.Json; +using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Messages; using Xunit; @@ -16,7 +17,7 @@ public void SimpleTest(string expected) result.Should().Be(expected); - var deresult = JsonConvert.DeserializeObject(expected); + var deresult = JsonConvert.DeserializeObject(expected); deresult.ShouldBeEquivalentTo(model); } } diff --git a/test/Lsp.Tests/Messages/ServerNotInitializedTests_$SimpleTest.json b/test/Lsp.Tests/Messages/ServerNotInitializedTests_$SimpleTest.json index f7707b87b..75b50bf23 100644 --- a/test/Lsp.Tests/Messages/ServerNotInitializedTests_$SimpleTest.json +++ b/test/Lsp.Tests/Messages/ServerNotInitializedTests_$SimpleTest.json @@ -1,7 +1,7 @@ -{ +{ "protocolVersion": "2.0", - "message": { + "error": { "code": -32002, "message": "Server Not Initialized" } -} \ No newline at end of file +} diff --git a/test/Lsp.Tests/Messages/UnknownErrorCodeTests.cs b/test/Lsp.Tests/Messages/UnknownErrorCodeTests.cs index 234012395..8441cd53e 100644 --- a/test/Lsp.Tests/Messages/UnknownErrorCodeTests.cs +++ b/test/Lsp.Tests/Messages/UnknownErrorCodeTests.cs @@ -1,6 +1,7 @@ using System; using FluentAssertions; using Newtonsoft.Json; +using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Messages; using Xunit; @@ -16,7 +17,7 @@ public void SimpleTest(string expected) result.Should().Be(expected); - var deresult = JsonConvert.DeserializeObject(expected); + var deresult = JsonConvert.DeserializeObject(expected); deresult.ShouldBeEquivalentTo(model); } } diff --git a/test/Lsp.Tests/Messages/UnknownErrorCodeTests_$SimpleTest.json b/test/Lsp.Tests/Messages/UnknownErrorCodeTests_$SimpleTest.json index f3315ea38..226f3809b 100644 --- a/test/Lsp.Tests/Messages/UnknownErrorCodeTests_$SimpleTest.json +++ b/test/Lsp.Tests/Messages/UnknownErrorCodeTests_$SimpleTest.json @@ -1,7 +1,7 @@ -{ +{ "protocolVersion": "2.0", - "message": { + "error": { "code": -32602, "message": "Unknown Error Code" } -} \ No newline at end of file +} From f1c675ab2683c79e3c4beb4103fb9b2006510906 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 19 Oct 2017 14:05:58 -0400 Subject: [PATCH 2/2] Fixed an issue where the error object was incorrectly shaped as per the spec --- src/JsonRpc/Client/Response.cs | 10 ----- src/JsonRpc/HandlerCollection.cs | 14 +------ src/JsonRpc/InputHandler.cs | 15 ++++--- src/JsonRpc/Reciever.cs | 4 +- src/JsonRpc/RequestRouter.cs | 19 +++++---- src/JsonRpc/RpcError.cs | 1 - src/JsonRpc/Server/Messages/MethodNotFound.cs | 2 +- src/JsonRpc/Server/Renor.cs | 11 +++-- src/JsonRpc/Server/Response.cs | 31 -------------- src/JsonRpc/Server/ServerResponse.cs | 38 ++++++++++++++++++ .../Client/SynchronizationCapability.cs | 6 ++- .../Server/SignatureHelpOptions.cs | 4 +- .../Server/SynchronizationOptions.cs | 38 ++++++++++++++++++ src/Lsp/LanguageServer.cs | 22 +++++----- src/Lsp/LspRequestRouter.cs | 40 +++++++++++++------ src/Lsp/Models/DocumentSelector.cs | 5 +++ src/Lsp/Models/ISignatureHelpOptions.cs | 24 ++++++++++- .../Document/TextDocumentAttributes.cs | 40 +++++++++++++++++-- test/JsonRpc.Tests/HandlerResolverTests.cs | 4 +- test/JsonRpc.Tests/InputHandlerTests.cs | 2 +- test/JsonRpc.Tests/TestLoggerFactory.cs | 1 - test/Lsp.Tests/LspRequestRouterTests.cs | 16 ++++++-- .../MediatorTestsRequestHandlerOfTRequest.cs | 10 ++++- ...rTestsRequestHandlerOfTRequestTResponse.cs | 10 ++++- 24 files changed, 248 insertions(+), 119 deletions(-) delete mode 100644 src/JsonRpc/Server/Response.cs create mode 100644 src/JsonRpc/Server/ServerResponse.cs create mode 100644 src/Lsp/Capabilities/Server/SynchronizationOptions.cs diff --git a/src/JsonRpc/Client/Response.cs b/src/JsonRpc/Client/Response.cs index 8a672a2ee..715a0c5ae 100644 --- a/src/JsonRpc/Client/Response.cs +++ b/src/JsonRpc/Client/Response.cs @@ -17,20 +17,10 @@ public Response(object id, object result) Result = result; } - public Response(object id, RpcError result) - { - Id = id; - Result = result; - } - public string ProtocolVersion { get; set; } = "2.0"; public object Id { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public object Result { get; set; } - - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public RpcError Error { get; set; } } } diff --git a/src/JsonRpc/HandlerCollection.cs b/src/JsonRpc/HandlerCollection.cs index ad72a34a8..54d68e21c 100644 --- a/src/JsonRpc/HandlerCollection.cs +++ b/src/JsonRpc/HandlerCollection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -66,16 +66,6 @@ public IDisposable Add(IJsonRpcHandler handler) return h; } - public IHandlerInstance Get(IJsonRpcHandler handler) - { - return _handlers.Find(instance => instance.Handler == handler); - } - - public IHandlerInstance Get(string method) - { - return _handlers.Find(instance => instance.Method == method); - } - private static readonly Type[] HandlerTypes = { typeof(INotificationHandler), typeof(INotificationHandler<>), typeof(IRequestHandler<>), typeof(IRequestHandler<,>), }; private string GetMethodName(Type type) @@ -115,4 +105,4 @@ private Type GetHandlerInterface(Type type) .First(IsValidInterface); } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/InputHandler.cs b/src/JsonRpc/InputHandler.cs index d20566eef..1849b74c7 100644 --- a/src/JsonRpc/InputHandler.cs +++ b/src/JsonRpc/InputHandler.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json.Linq; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using OmniSharp.Extensions.JsonRpc.Server; using OmniSharp.Extensions.JsonRpc.Server.Messages; namespace OmniSharp.Extensions.JsonRpc @@ -148,13 +149,13 @@ private void HandleRequest(string request) var tcs = _responseRouter.GetRequest(id); if (tcs is null) continue; - if (response.Error is null) + if (response is ServerResponse serverResponse) { - tcs.SetResult(response.Result); + tcs.SetResult(serverResponse.Result); } - else + else if (response is ServerError serverError) { - tcs.SetException(new Exception(JsonConvert.SerializeObject(response.Error))); + tcs.SetException(new Exception(JsonConvert.SerializeObject(serverError.Error))); } } @@ -168,8 +169,7 @@ private void HandleRequest(string request) _scheduler.Add( type, item.Request.Method, - async () => - { + async () => { try { var result = await _requestRouter.RouteRequest(item.Request); @@ -190,8 +190,7 @@ private void HandleRequest(string request) _scheduler.Add( type, item.Notification.Method, - () => - { + () => { try { _requestRouter.RouteNotification(item.Notification); diff --git a/src/JsonRpc/Reciever.cs b/src/JsonRpc/Reciever.cs index 72d233c19..a7b547763 100644 --- a/src/JsonRpc/Reciever.cs +++ b/src/JsonRpc/Reciever.cs @@ -63,13 +63,13 @@ protected virtual Renor GetRenor(JToken @object) if (hasRequestId && request.TryGetValue("result", out var response)) { - return new Response(requestId, response); + return new ServerResponse(requestId, response); } if (hasRequestId && request.TryGetValue("error", out var errorResponse)) { // TODO: this doesn't seem right. - return new RpcError(requestId, new ErrorMessage(-1337, "Unknown error response", errorResponse)); + return new ServerError(requestId, errorResponse); } var method = request["method"]?.Value(); diff --git a/src/JsonRpc/RequestRouter.cs b/src/JsonRpc/RequestRouter.cs index 35bbf230c..44e8c3aa8 100644 --- a/src/JsonRpc/RequestRouter.cs +++ b/src/JsonRpc/RequestRouter.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -23,7 +24,7 @@ public IDisposable Add(IJsonRpcHandler handler) public async void RouteNotification(Notification notification) { - var handler = _collection.Get(notification.Method); + var handler = _collection.FirstOrDefault(x => x.Method == notification.Method); Task result; if (handler.Params is null) @@ -46,16 +47,14 @@ public Task RouteRequest(Request request) protected virtual async Task RouteRequest(Request request, CancellationToken token) { - var handler = _collection.Get(request.Method); - - var method = _collection.Get(request.Method); - if (method is null) + var handler = _collection.FirstOrDefault(x => x.Method == request.Method); + if (request.Method is null) { - return new MethodNotFound(request.Id); + return new MethodNotFound(request.Id, request.Method); } Task result; - if (method.Params is null) + if (handler.Params is null) { result = ReflectionRequestHandlers.HandleRequest(handler, token); } @@ -64,7 +63,7 @@ protected virtual async Task RouteRequest(Request request, Cancel object @params; try { - @params = request.Params.ToObject(method.Params); + @params = request.Params.ToObject(handler.Params); } catch { @@ -89,4 +88,4 @@ protected virtual async Task RouteRequest(Request request, Cancel return new Client.Response(request.Id, responseValue); } } -} \ No newline at end of file +} diff --git a/src/JsonRpc/RpcError.cs b/src/JsonRpc/RpcError.cs index ba7ddb479..492799fcd 100644 --- a/src/JsonRpc/RpcError.cs +++ b/src/JsonRpc/RpcError.cs @@ -25,7 +25,6 @@ public RpcError(object id, ErrorMessage message, string protocolVersion) [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public object Id { get; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public ErrorMessage Error { get; } } diff --git a/src/JsonRpc/Server/Messages/MethodNotFound.cs b/src/JsonRpc/Server/Messages/MethodNotFound.cs index bc1c37f24..c774a39d4 100644 --- a/src/JsonRpc/Server/Messages/MethodNotFound.cs +++ b/src/JsonRpc/Server/Messages/MethodNotFound.cs @@ -2,6 +2,6 @@ { public class MethodNotFound : RpcError { - public MethodNotFound(object id) : base(id, new ErrorMessage(-32601, "Method not found")) { } + public MethodNotFound(object id, string method) : base(id, new ErrorMessage(-32601, $"Method not found - {method}")) { } } } diff --git a/src/JsonRpc/Server/Renor.cs b/src/JsonRpc/Server/Renor.cs index deab1a5a0..a0362e522 100644 --- a/src/JsonRpc/Server/Renor.cs +++ b/src/JsonRpc/Server/Renor.cs @@ -30,7 +30,7 @@ internal Renor(RpcError errorMessage) Response = null; } - internal Renor(Response response) + internal Renor(ResponseBase response) { Notification = null; Request = null; @@ -48,7 +48,7 @@ internal Renor(Response response) public RpcError Error { get; } public bool IsResponse => Response != null; - public Response Response { get; } + public ResponseBase Response { get; } public static implicit operator Renor(Notification notification) { @@ -65,7 +65,12 @@ public static implicit operator Renor(RpcError error) return new Renor(error); } - public static implicit operator Renor(Response response) + public static implicit operator Renor(ServerResponse response) + { + return new Renor(response); + } + + public static implicit operator Renor(ServerError response) { return new Renor(response); } diff --git a/src/JsonRpc/Server/Response.cs b/src/JsonRpc/Server/Response.cs deleted file mode 100644 index f29af46fb..000000000 --- a/src/JsonRpc/Server/Response.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OmniSharp.Extensions.JsonRpc.Server.Messages; - -namespace OmniSharp.Extensions.JsonRpc.Server -{ - public class Response - { - public Response(object id, JToken result) - { - Id = id; - Result = result; - } - - public Response(object id, IErrorMessage error) - { - Id = id; - Error = error; - } - - public string ProtocolVersion { get; set; } = "2.0"; - - public object Id { get; set; } - - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public JToken Result { get; set; } - - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public IErrorMessage Error { get; set; } - } -} diff --git a/src/JsonRpc/Server/ServerResponse.cs b/src/JsonRpc/Server/ServerResponse.cs new file mode 100644 index 000000000..5df7d1d45 --- /dev/null +++ b/src/JsonRpc/Server/ServerResponse.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc.Server.Messages; + +namespace OmniSharp.Extensions.JsonRpc.Server +{ + public class ResponseBase + { + public ResponseBase(object id) + { + Id = id; + } + + public string ProtocolVersion { get; set; } = "2.0"; + + public object Id { get; set; } + } + + public class ServerResponse : ResponseBase + { + public ServerResponse(object id, JToken result) : base(id) + { + Result = result; + } + + public JToken Result { get; set; } + } + + public class ServerError : ResponseBase + { + public ServerError(object id, JToken result) : base(id) + { + Error = result; + } + + public JToken Error { get; set; } + } +} diff --git a/src/Lsp/Capabilities/Client/SynchronizationCapability.cs b/src/Lsp/Capabilities/Client/SynchronizationCapability.cs index 252d57d30..41800f513 100644 --- a/src/Lsp/Capabilities/Client/SynchronizationCapability.cs +++ b/src/Lsp/Capabilities/Client/SynchronizationCapability.cs @@ -1,6 +1,8 @@ -namespace OmniSharp.Extensions.LanguageServer.Capabilities.Client +using OmniSharp.Extensions.LanguageServer.Protocol.Document; + +namespace OmniSharp.Extensions.LanguageServer.Capabilities.Client { - public class SynchronizationCapability : DynamicCapability + public class SynchronizationCapability : DynamicCapability, ConnectedCapability { /// /// The client supports sending will save notifications. diff --git a/src/Lsp/Capabilities/Server/SignatureHelpOptions.cs b/src/Lsp/Capabilities/Server/SignatureHelpOptions.cs index cce0f62d5..5da2e79e5 100644 --- a/src/Lsp/Capabilities/Server/SignatureHelpOptions.cs +++ b/src/Lsp/Capabilities/Server/SignatureHelpOptions.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using OmniSharp.Extensions.LanguageServer.Models; @@ -22,4 +22,4 @@ public static SignatureHelpOptions Of(ISignatureHelpOptions options) return new SignatureHelpOptions() { TriggerCharacters = options.TriggerCharacters }; } } -} \ No newline at end of file +} diff --git a/src/Lsp/Capabilities/Server/SynchronizationOptions.cs b/src/Lsp/Capabilities/Server/SynchronizationOptions.cs new file mode 100644 index 000000000..049c07cb8 --- /dev/null +++ b/src/Lsp/Capabilities/Server/SynchronizationOptions.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using OmniSharp.Extensions.LanguageServer.Models; + +namespace OmniSharp.Extensions.LanguageServer.Capabilities.Server +{ + /// + /// Signature help options. + /// + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class SynchronizationOptions : ISynchronizationOptions + { + /// + /// The client supports sending will save notifications. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public bool WillSave { get; set; } + + /// + /// The client supports sending a will save request and + /// waits for a response providing text edits which will + /// be applied to the document before it is saved. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public bool WillSaveWaitUntil { get; set; } + + /// + /// The client supports did save notifications. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public bool DidSave { get; set; } + + public static SynchronizationOptions Of(ISynchronizationOptions options) + { + return new SynchronizationOptions() { WillSave = options.WillSave, DidSave = options.DidSave, WillSaveWaitUntil = options.WillSaveWaitUntil }; + } + } +} \ No newline at end of file diff --git a/src/Lsp/LanguageServer.cs b/src/Lsp/LanguageServer.cs index 7f77a8cef..ba803ec7d 100644 --- a/src/Lsp/LanguageServer.cs +++ b/src/Lsp/LanguageServer.cs @@ -45,7 +45,7 @@ internal LanguageServer(Stream input, IOutputHandler output, LspReciever recieve _reciever = reciever; _loggerFactory = loggerFactory; - _requestRouter = new LspRequestRouter(_collection); + _requestRouter = new LspRequestRouter(_collection, loggerFactory); _responseRouter = new ResponseRouter(output); _connection = new Connection(input, output, reciever, requestProcessIdentifier, _requestRouter, _responseRouter, loggerFactory); @@ -168,14 +168,17 @@ async Task IRequestHandler } else { - // TODO: Merge options - serverCapabilities.TextDocumentSync = textSyncHandlers.FirstOrDefault()?.Options ?? new TextDocumentSyncOptions() { - Change = TextDocumentSyncKind.None, - OpenClose = false, - Save = new SaveOptions() { IncludeText = false }, - WillSave = false, - WillSaveWaitUntil = false - }; + if (ccp.HasHandler(textDocumentCapabilities.Synchronization)) + { + // TODO: Merge options + serverCapabilities.TextDocumentSync = textSyncHandlers.FirstOrDefault()?.Options ?? new TextDocumentSyncOptions() { + Change = TextDocumentSyncKind.None, + OpenClose = false, + Save = new SaveOptions() { IncludeText = false }, + WillSave = false, + WillSaveWaitUntil = false + }; + } } _reciever.Initialized(); @@ -286,3 +289,4 @@ public void Dispose() public IDictionary Experimental { get; } = new Dictionary(); } } + diff --git a/src/Lsp/LspRequestRouter.cs b/src/Lsp/LspRequestRouter.cs index 8f04385f2..656141805 100644 --- a/src/Lsp/LspRequestRouter.cs +++ b/src/Lsp/LspRequestRouter.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Server; @@ -19,12 +20,13 @@ namespace OmniSharp.Extensions.LanguageServer class LspRequestRouter : IRequestRouter { private readonly IHandlerCollection _collection; - private ITextDocumentSyncHandler[] _textDocumentSyncHandlers; private readonly ConcurrentDictionary _requests = new ConcurrentDictionary(); + private readonly ILogger _logger; - public LspRequestRouter(IHandlerCollection collection) + public LspRequestRouter(IHandlerCollection collection, ILoggerFactory loggerFactory) { _collection = collection; + _logger = loggerFactory.CreateLogger(); } private string GetId(object id) @@ -44,23 +46,28 @@ private string GetId(object id) private ILspHandlerDescriptor FindDescriptor(string method, JToken @params) { - var descriptor = _collection.FirstOrDefault(x => x.Method.Equals(method, StringComparison.OrdinalIgnoreCase)); - if (descriptor is null) return null; - - if (_textDocumentSyncHandlers == null) + _logger.LogDebug("Finding descriptor for {Method}", method); + var descriptor = _collection.FirstOrDefault(x => x.Method == method); + if (descriptor is null) { - _textDocumentSyncHandlers = _collection - .Select(x => x.Handler is ITextDocumentSyncHandler r ? r : null) - .Where(x => x != null) - .ToArray(); + _logger.LogDebug("Unable to find {Method}, methods found include {Methods}", method, string.Join(", ", _collection.Select(x => x.Method + ":" + x.Handler.GetType().FullName))); + return null; } if (typeof(ITextDocumentIdentifierParams).GetTypeInfo().IsAssignableFrom(descriptor.Params)) { var textDocumentIdentifierParams = @params.ToObject(descriptor.Params) as ITextDocumentIdentifierParams; - var attributes = _textDocumentSyncHandlers + var textDocumentSyncHandlers = _collection + .Select(x => x.Handler is ITextDocumentSyncHandler r ? r : null) + .Where(x => x != null) + .Distinct(); + var attributes = textDocumentSyncHandlers .Select(x => x.GetTextDocumentAttributes(textDocumentIdentifierParams.TextDocument.Uri)) - .Where(x => x != null); + .Where(x => x != null) + .Distinct() + .ToList(); + + _logger.LogTrace("Found attributes {Count}, {Attributes}", attributes.Count, attributes.Select(x => $"{x.LanguageId}:{x.Scheme}:{x.Uri}")); return GetHandler(method, attributes); } @@ -69,6 +76,8 @@ private ILspHandlerDescriptor FindDescriptor(string method, JToken @params) var openTextDocumentParams = @params.ToObject(descriptor.Params) as DidOpenTextDocumentParams; var attributes = new TextDocumentAttributes(openTextDocumentParams.TextDocument.Uri, openTextDocumentParams.TextDocument.LanguageId); + _logger.LogTrace("Created attribute {Attribute}", $"{attributes.LanguageId}:{attributes.Scheme}:{attributes.Uri}"); + return GetHandler(method, attributes); } @@ -86,9 +95,14 @@ private ILspHandlerDescriptor GetHandler(string method, IEnumerable x.Method == method)) { + _logger.LogTrace("Checking handler {Method}:{Handler}", method, handler.Handler.GetType().FullName); var registrationOptions = handler.Registration.RegisterOptions as TextDocumentRegistrationOptions; + + _logger.LogTrace("Registration options {OptionsName}", registrationOptions.GetType().FullName); + _logger.LogTrace("Document Selector {DocumentSelector}", registrationOptions.DocumentSelector.ToString()); if (registrationOptions.DocumentSelector == null || registrationOptions.DocumentSelector.IsMatch(attributes)) { return handler; @@ -128,7 +142,7 @@ public async Task RouteRequest(Request request) var method = FindDescriptor(request.Method, request.Params); if (method is null) { - return new MethodNotFound(request.Id); + return new MethodNotFound(request.Id, request.Method); } Task result; diff --git a/src/Lsp/Models/DocumentSelector.cs b/src/Lsp/Models/DocumentSelector.cs index eeef9a0cb..8a4b0e8a8 100644 --- a/src/Lsp/Models/DocumentSelector.cs +++ b/src/Lsp/Models/DocumentSelector.cs @@ -48,6 +48,11 @@ public bool IsMatch(TextDocumentAttributes attributes) return this.Any(z => z.IsMatch(attributes)); } + public override string ToString() + { + return this; + } + public static DocumentSelector ForPattern(params string[] wildcards) { return new DocumentSelector(wildcards.Select(DocumentFilter.ForPattern)); diff --git a/src/Lsp/Models/ISignatureHelpOptions.cs b/src/Lsp/Models/ISignatureHelpOptions.cs index 9062b0dda..58f0cd10b 100644 --- a/src/Lsp/Models/ISignatureHelpOptions.cs +++ b/src/Lsp/Models/ISignatureHelpOptions.cs @@ -1,7 +1,27 @@ -namespace OmniSharp.Extensions.LanguageServer.Models +namespace OmniSharp.Extensions.LanguageServer.Models { public interface ISignatureHelpOptions { Container TriggerCharacters { get; set; } } -} \ No newline at end of file + + public interface ISynchronizationOptions + { + /// + /// The client supports sending will save notifications. + /// + bool WillSave { get; } + + /// + /// The client supports sending a will save request and + /// waits for a response providing text edits which will + /// be applied to the document before it is saved. + /// + bool WillSaveWaitUntil { get; } + + /// + /// The client supports did save notifications. + /// + bool DidSave { get; } + } +} diff --git a/src/Lsp/Protocol/Document/TextDocumentAttributes.cs b/src/Lsp/Protocol/Document/TextDocumentAttributes.cs index ffc7e0d3c..8bae913ea 100644 --- a/src/Lsp/Protocol/Document/TextDocumentAttributes.cs +++ b/src/Lsp/Protocol/Document/TextDocumentAttributes.cs @@ -1,13 +1,13 @@ -using System; +using System; +using System.Collections.Generic; namespace OmniSharp.Extensions.LanguageServer.Protocol.Document { - public class TextDocumentAttributes + public class TextDocumentAttributes : IEquatable { public TextDocumentAttributes(Uri uri, string languageId) { Uri = uri; - Scheme = uri.Scheme; LanguageId = languageId; } @@ -21,5 +21,37 @@ public TextDocumentAttributes(Uri uri, string scheme, string languageId) public Uri Uri { get; } public string Scheme { get; } public string LanguageId { get; } + + public override bool Equals(object obj) + { + return Equals(obj as TextDocumentAttributes); + } + + public bool Equals(TextDocumentAttributes other) + { + return other != null && + EqualityComparer.Default.Equals(Uri, other.Uri) && + Scheme == other.Scheme && + LanguageId == other.LanguageId; + } + + public override int GetHashCode() + { + var hashCode = -918855467; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Uri); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Scheme); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(LanguageId); + return hashCode; + } + + public static bool operator ==(TextDocumentAttributes attributes1, TextDocumentAttributes attributes2) + { + return EqualityComparer.Default.Equals(attributes1, attributes2); + } + + public static bool operator !=(TextDocumentAttributes attributes1, TextDocumentAttributes attributes2) + { + return !(attributes1 == attributes2); + } } -} \ No newline at end of file +} diff --git a/test/JsonRpc.Tests/HandlerResolverTests.cs b/test/JsonRpc.Tests/HandlerResolverTests.cs index 16db136a4..be7d21bfd 100644 --- a/test/JsonRpc.Tests/HandlerResolverTests.cs +++ b/test/JsonRpc.Tests/HandlerResolverTests.cs @@ -1,8 +1,10 @@ using System; +using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Common; using NSubstitute; using OmniSharp.Extensions.JsonRpc; using Xunit; @@ -48,7 +50,7 @@ public void Should_Have_CorrectParams(Type requestHandler, string key, Type expe { var handler = new HandlerCollection(); handler.Add((IJsonRpcHandler)Substitute.For(new Type[] { requestHandler }, new object[0])); - handler.Get(key).Params.Should().Equals(expected); + handler.First(x => x.Method == key).Params.Should().IsSameOrEqualTo(expected); } } } diff --git a/test/JsonRpc.Tests/InputHandlerTests.cs b/test/JsonRpc.Tests/InputHandlerTests.cs index 53867c1b3..3af28e8e6 100644 --- a/test/JsonRpc.Tests/InputHandlerTests.cs +++ b/test/JsonRpc.Tests/InputHandlerTests.cs @@ -246,7 +246,7 @@ public void ShouldHandleResponse() var reciever = Substitute.For(); var responseRouter = Substitute.For(); - var response = new OmniSharp.Extensions.JsonRpc.Server.Response(1L, JToken.Parse("{}")); + var response = new OmniSharp.Extensions.JsonRpc.Server.ServerResponse(1L, JToken.Parse("{}")); reciever.IsValid(Arg.Any()).Returns(true); reciever.GetRequests(Arg.Any()) .Returns(c => (new Renor[] { response }, true)); diff --git a/test/JsonRpc.Tests/TestLoggerFactory.cs b/test/JsonRpc.Tests/TestLoggerFactory.cs index db1d747cf..3e9d48b35 100644 --- a/test/JsonRpc.Tests/TestLoggerFactory.cs +++ b/test/JsonRpc.Tests/TestLoggerFactory.cs @@ -9,7 +9,6 @@ namespace JsonRpc.Tests { public class TestLoggerFactory : ILoggerFactory { - private readonly ITestOutputHelper _testOutputHelper; private readonly SerilogLoggerProvider _loggerProvider; public TestLoggerFactory(ITestOutputHelper testOutputHelper) diff --git a/test/Lsp.Tests/LspRequestRouterTests.cs b/test/Lsp.Tests/LspRequestRouterTests.cs index ecf4245f5..4c506c3da 100644 --- a/test/Lsp.Tests/LspRequestRouterTests.cs +++ b/test/Lsp.Tests/LspRequestRouterTests.cs @@ -15,12 +15,20 @@ using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using Xunit; +using Xunit.Abstractions; using Xunit.Sdk; namespace Lsp.Tests { public class LspRequestRouterTests { + private readonly TestLoggerFactory _testLoggerFactory; + + public LspRequestRouterTests(ITestOutputHelper testOutputHelper) + { + _testLoggerFactory = new TestLoggerFactory(testOutputHelper); + } + [Fact] public void ShouldRouteToCorrect_Notification() { @@ -28,7 +36,7 @@ public void ShouldRouteToCorrect_Notification() textDocumentSyncHandler.Handle(Arg.Any()).Returns(Task.CompletedTask); var collection = new HandlerCollection { textDocumentSyncHandler }; - var mediator = new LspRequestRouter(collection); + var mediator = new LspRequestRouter(collection, _testLoggerFactory); var @params = new DidSaveTextDocumentParams() { TextDocument = new TextDocumentIdentifier(new Uri("file:///c:/test/123.cs")) @@ -50,7 +58,7 @@ public void ShouldRouteToCorrect_Notification_WithManyHandlers() textDocumentSyncHandler2.Handle(Arg.Any()).Returns(Task.CompletedTask); var collection = new HandlerCollection { textDocumentSyncHandler, textDocumentSyncHandler2 }; - var mediator = new LspRequestRouter(collection); + var mediator = new LspRequestRouter(collection, _testLoggerFactory); var @params = new DidSaveTextDocumentParams() { TextDocument = new TextDocumentIdentifier(new Uri("file:///c:/test/123.cake")) @@ -77,7 +85,7 @@ public async Task ShouldRouteToCorrect_Request() .Returns(new CommandContainer()); var collection = new HandlerCollection { textDocumentSyncHandler, codeActionHandler }; - var mediator = new LspRequestRouter(collection); + var mediator = new LspRequestRouter(collection, _testLoggerFactory); var id = Guid.NewGuid().ToString(); var @params = new DidSaveTextDocumentParams() { @@ -112,7 +120,7 @@ public async Task ShouldRouteToCorrect_Request_WithManyHandlers() .Returns(new CommandContainer()); var collection = new HandlerCollection { textDocumentSyncHandler, textDocumentSyncHandler2, codeActionHandler , codeActionHandler2 }; - var mediator = new LspRequestRouter(collection); + var mediator = new LspRequestRouter(collection, _testLoggerFactory); var id = Guid.NewGuid().ToString(); var @params = new DidSaveTextDocumentParams() { diff --git a/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs b/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs index 8033d5e7f..dea24ff7e 100644 --- a/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs +++ b/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequest.cs @@ -13,6 +13,7 @@ using OmniSharp.Extensions.LanguageServer.Messages; using OmniSharp.Extensions.LanguageServer.Models; using Xunit; +using Xunit.Abstractions; using Xunit.Sdk; using HandlerCollection = OmniSharp.Extensions.LanguageServer.HandlerCollection; @@ -20,6 +21,13 @@ namespace Lsp.Tests { public class MediatorTestsRequestHandlerOfTRequest { + private readonly TestLoggerFactory _testLoggerFactory; + + public MediatorTestsRequestHandlerOfTRequest(ITestOutputHelper testOutputHelper) + { + _testLoggerFactory = new TestLoggerFactory(testOutputHelper); + } + [Fact] public async Task RequestsCancellation() { @@ -32,7 +40,7 @@ public async Task RequestsCancellation() }); var collection = new HandlerCollection { executeCommandHandler }; - var mediator = new LspRequestRouter(collection); + var mediator = new LspRequestRouter(collection, _testLoggerFactory); var id = Guid.NewGuid().ToString(); var @params = new ExecuteCommandParams() { Command = "123" }; diff --git a/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs b/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs index 5df0280b0..577f928e6 100644 --- a/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs +++ b/test/Lsp.Tests/MediatorTestsRequestHandlerOfTRequestTResponse.cs @@ -15,6 +15,7 @@ using OmniSharp.Extensions.LanguageServer.Messages; using OmniSharp.Extensions.LanguageServer.Models; using Xunit; +using Xunit.Abstractions; using Xunit.Sdk; using HandlerCollection = OmniSharp.Extensions.LanguageServer.HandlerCollection; @@ -22,6 +23,13 @@ namespace Lsp.Tests { public class MediatorTestsRequestHandlerOfTRequestTResponse { + private readonly TestLoggerFactory _testLoggerFactory; + + public MediatorTestsRequestHandlerOfTRequestTResponse(ITestOutputHelper testOutputHelper) + { + _testLoggerFactory = new TestLoggerFactory(testOutputHelper); + } + [Fact] public async Task RequestsCancellation() { @@ -39,7 +47,7 @@ public async Task RequestsCancellation() }); var collection = new HandlerCollection { textDocumentSyncHandler, codeActionHandler }; - var mediator = new LspRequestRouter(collection); + var mediator = new LspRequestRouter(collection, _testLoggerFactory); var id = Guid.NewGuid().ToString(); var @params = new CodeActionParams() {