Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for strongly typed data to be attached to */resolve #250

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions sample/SampleServer/TextDocumentHandler.cs
Expand Up @@ -100,9 +100,9 @@ TextDocumentSaveRegistrationOptions IRegistration<TextDocumentSaveRegistrationOp
};
}

public TextDocumentAttributes GetTextDocumentAttributes(DocumentUri uri)
public IEnumerable<TextDocumentAttributes> GetTextDocumentAttributes(DocumentUri uri)
{
return new TextDocumentAttributes(uri, "csharp");
yield return new TextDocumentAttributes(uri, "csharp");
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/JsonRpc.Testing/IRequestSettler.cs
Expand Up @@ -5,4 +5,4 @@ public interface IRequestSettler
void OnStartRequest();
void OnEndRequest();
}
}
}
38 changes: 34 additions & 4 deletions src/JsonRpc/IRequestRouter.cs
@@ -1,4 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OmniSharp.Extensions.JsonRpc.Server;
Expand All @@ -10,11 +13,38 @@ public interface IRequestRouter
IServiceProvider ServiceProvider { get; }
}

public interface IRequestDescriptor<out TDescriptor> : IEnumerable<TDescriptor>
{
TDescriptor Default { get; }
}

class RequestDescriptor<TDescriptor> : IRequestDescriptor<TDescriptor>
{
private IEnumerable<TDescriptor> _descriptors;

public RequestDescriptor(IEnumerable<TDescriptor> descriptors)
{
var enumerable = descriptors as TDescriptor[] ?? descriptors.ToArray();
_descriptors = enumerable;
Default = enumerable.FirstOrDefault();
}

public IEnumerator<TDescriptor> GetEnumerator() => _descriptors.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) _descriptors).GetEnumerator();

public TDescriptor Default { get; }
}

public interface IRequestRouter<TDescriptor> : IRequestRouter
{
TDescriptor GetDescriptor(Notification notification);
TDescriptor GetDescriptor(Request request);
Task RouteNotification(TDescriptor descriptor, Notification notification, CancellationToken token);
Task<ErrorResponse> RouteRequest(TDescriptor descriptor, Request request, CancellationToken token);
IRequestDescriptor<TDescriptor> GetDescriptor(Notification notification);
IRequestDescriptor<TDescriptor> GetDescriptor(Request request);
Task RouteNotification(IRequestDescriptor<TDescriptor> descriptors, Notification notification, CancellationToken token);
Task<ErrorResponse> RouteRequest(IRequestDescriptor<TDescriptor> descriptors, Request request, CancellationToken token);
}

interface IAggregateResults
{
object AggregateResults(IEnumerable<object> values);
}
}
16 changes: 8 additions & 8 deletions src/JsonRpc/InputHandler.cs
Expand Up @@ -54,8 +54,8 @@ public class InputHandler : IInputHandler, IDisposable
private readonly CompositeDisposable _disposable;
private readonly AsyncSubject<Unit> _inputActive;

private readonly ConcurrentDictionary<object, (CancellationTokenSource cancellationTokenSource, IHandlerDescriptor descriptor)> _requests =
new ConcurrentDictionary<object, (CancellationTokenSource cancellationTokenSource, IHandlerDescriptor descriptor)>();
private readonly ConcurrentDictionary<object, (CancellationTokenSource cancellationTokenSource, IRequestDescriptor<IHandlerDescriptor> descriptor)> _requests =
new ConcurrentDictionary<object, (CancellationTokenSource cancellationTokenSource, IRequestDescriptor<IHandlerDescriptor> descriptor)>();

private readonly Subject<IObservable<Unit>> _inputQueue;

Expand Down Expand Up @@ -411,14 +411,14 @@ private void HandleRequest(in ReadOnlySequence<byte> request)
{
// _logger.LogDebug("Handling Request {Method} {ResponseId}", item.Request.Method, item.Request.Id);
var descriptor = _requestRouter.GetDescriptor(item.Request);
if (descriptor is null)
if (descriptor.Default is null)
{
_logger.LogDebug("Request handler was not found (or not setup) {Method} {ResponseId}", item.Request.Method, item.Request.Id);
_outputHandler.Send(new MethodNotFound(item.Request.Id, item.Request.Method));
return;
}

var type = _requestProcessIdentifier.Identify(descriptor);
var type = _requestProcessIdentifier.Identify(descriptor.Default);
_scheduler.Add(type, $"{item.Request.Method}:{item.Request.Id}", RouteRequest(descriptor, item.Request));
}

Expand Down Expand Up @@ -446,15 +446,15 @@ private void HandleRequest(in ReadOnlySequence<byte> request)

// _logger.LogDebug("Handling Request {Method}", item.Notification.Method);
var descriptor = _requestRouter.GetDescriptor(item.Notification);
if (descriptor is null)
if (descriptor.Default is null)
{
_logger.LogDebug("Notification handler was not found (or not setup) {Method}", item.Notification.Method);
// TODO: Figure out a good way to send this feedback back.
// _outputHandler.Send(new RpcError(null, new ErrorMessage(-32601, $"Method not found - {item.Notification.Method}")));
return;
}

var type = _requestProcessIdentifier.Identify(descriptor);
var type = _requestProcessIdentifier.Identify(descriptor.Default);
_scheduler.Add(type, item.Notification.Method, RouteNotification(descriptor, item.Notification));
}

Expand All @@ -465,7 +465,7 @@ private void HandleRequest(in ReadOnlySequence<byte> request)
}
}

private SchedulerDelegate RouteRequest(IHandlerDescriptor descriptor, Request request)
private SchedulerDelegate RouteRequest(IRequestDescriptor<IHandlerDescriptor> descriptor, Request request)
{
// start request, create cts, etc
var cts = new CancellationTokenSource();
Expand Down Expand Up @@ -521,7 +521,7 @@ private SchedulerDelegate RouteRequest(IHandlerDescriptor descriptor, Request re
});
}

private SchedulerDelegate RouteNotification(IHandlerDescriptor descriptor, Notification notification)
private SchedulerDelegate RouteNotification(IRequestDescriptor<IHandlerDescriptor> descriptor, Notification notification)
{
return (contentModifiedToken, scheduler) =>
// ITS A RACE!
Expand Down
9 changes: 4 additions & 5 deletions src/JsonRpc/NotificationHandler.cs
Expand Up @@ -5,20 +5,19 @@

namespace OmniSharp.Extensions.JsonRpc
{
public static class NotificationHandler
{
public static class NotificationHandler {

public static DelegatingHandlers.Notification<TParams> For<TParams>(Action<TParams, CancellationToken> handler)
where TParams : IRequest
{
return new DelegatingHandlers.Notification<TParams>(handler);
return new DelegatingHandlers.Notification<TParams>( handler);
}

public static DelegatingHandlers.Notification<TParams> For<TParams>(Func<TParams, CancellationToken, Task> handler)
where TParams : IRequest
{
return new DelegatingHandlers.Notification<TParams>(handler);
}

public static DelegatingHandlers.Notification<TParams> For<TParams>(Action<TParams> handler)
where TParams : IRequest
{
Expand All @@ -31,4 +30,4 @@ public static DelegatingHandlers.Notification<TParams> For<TParams>(Func<TParams
return new DelegatingHandlers.Notification<TParams>((p, ct) => handler(p));
}
}
}
}
8 changes: 4 additions & 4 deletions src/JsonRpc/RequestRouter.cs
Expand Up @@ -22,17 +22,17 @@ public IDisposable Add(IJsonRpcHandler handler)
return _collection.Add(handler);
}

private IHandlerDescriptor FindDescriptor(IMethodWithParams instance)
private IRequestDescriptor<IHandlerDescriptor> FindDescriptor(IMethodWithParams instance)
{
return _collection.FirstOrDefault(x => x.Method == instance.Method);
return new RequestDescriptor<IHandlerDescriptor>(_collection.Where(x => x.Method == instance.Method));
}

public override IHandlerDescriptor GetDescriptor(Notification notification)
public override IRequestDescriptor<IHandlerDescriptor> GetDescriptor(Notification notification)
{
return FindDescriptor(notification);
}

public override IHandlerDescriptor GetDescriptor(Request request)
public override IRequestDescriptor<IHandlerDescriptor> GetDescriptor(Request request)
{
return FindDescriptor(request);
}
Expand Down
75 changes: 51 additions & 24 deletions src/JsonRpc/RequestRouterBase.cs
Expand Up @@ -10,6 +10,7 @@
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using OmniSharp.Extensions.JsonRpc.Client;

namespace OmniSharp.Extensions.JsonRpc
{
Expand All @@ -30,50 +31,76 @@ public RequestRouterBase(ISerializer serializer, IServiceProvider serviceProvide

public IServiceProvider ServiceProvider { get; }

public async Task RouteNotification(TDescriptor descriptor, Notification notification, CancellationToken token)
public async Task RouteNotification(IRequestDescriptor<TDescriptor> descriptors, Notification notification, CancellationToken token)
{
using var debug = _logger.TimeDebug("Routing Notification {Method}", notification.Method);
using var _ = _logger.BeginScope(new[] {
new KeyValuePair<string, string>("Method", notification.Method),
new KeyValuePair<string, string>("Params", notification.Params?.ToString())
});
using var scope = _serviceScopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<IRequestContext>();
context.Descriptor = descriptor;
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();

if (descriptor.Params is null)
{
await HandleNotification(mediator, descriptor, EmptyRequest.Instance, token);
}
else
await Task.WhenAll(descriptors.Select(descriptor => InnerRouteNotification(descriptor)));

async Task InnerRouteNotification(TDescriptor descriptor)
{
_logger.LogDebug("Converting params for Notification {Method} to {Type}", notification.Method, descriptor.Params.FullName);
object @params;
if (descriptor.IsDelegatingHandler)
using var scope = _serviceScopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<IRequestContext>();
context.Descriptor = descriptor;
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();

if (descriptor.Params is null)
{
// new DelegatingRequest();
var o = notification.Params?.ToObject(descriptor.Params.GetGenericArguments()[0], _serializer.JsonSerializer);
@params = Activator.CreateInstance(descriptor.Params, new object[] {o});
await HandleNotification(mediator, descriptor, EmptyRequest.Instance, token);
}
else
{
@params = notification.Params?.ToObject(descriptor.Params, _serializer.JsonSerializer);
_logger.LogDebug("Converting params for Notification {Method} to {Type}", notification.Method, descriptor.Params.FullName);
object @params;
if (descriptor.IsDelegatingHandler)
{
// new DelegatingRequest();
var o = notification.Params?.ToObject(descriptor.Params.GetGenericArguments()[0], _serializer.JsonSerializer);
@params = Activator.CreateInstance(descriptor.Params, new object[] {o});
}
else
{
@params = notification.Params?.ToObject(descriptor.Params, _serializer.JsonSerializer);
}

await HandleNotification(mediator, descriptor, @params ?? Activator.CreateInstance(descriptor.Params), token);
}

await HandleNotification(mediator, descriptor, @params ?? Activator.CreateInstance(descriptor.Params), token);
}
}

public virtual async Task<ErrorResponse> RouteRequest(TDescriptor descriptor, Request request, CancellationToken token)
public virtual async Task<ErrorResponse> RouteRequest(IRequestDescriptor<TDescriptor> descriptors, Request request, CancellationToken token)
{
using var debug = _logger.TimeDebug("Routing Request ({Id}) {Method}", request.Id, request.Method);
using var _ = _logger.BeginScope(new[] {
new KeyValuePair<string, string>("Id", request.Id?.ToString()),
new KeyValuePair<string, string>("Method", request.Method),
new KeyValuePair<string, string>("Params", request.Params?.ToString())
});
using var scope = _serviceScopeFactory.CreateScope();

if (typeof(IAggregateResults).IsAssignableFrom(descriptors.Default.Response))
{
var responses = await Task.WhenAll(descriptors.Select(InnerRouteRequest));
var errorResponse = responses.FirstOrDefault(x => x.IsError);
if (errorResponse.IsError) return errorResponse;
if (responses.Length == 1)
{
return responses[0];
}

if (!(responses[0].Value is OutgoingResponse or)) throw new NotSupportedException("Unsupported response type");
if (!(or.Result is IAggregateResults ar)) throw new NotSupportedException("Unsupported result type");
return new OutgoingResponse(request.Id, ar.AggregateResults(responses.Skip(1).Select(z => z.Value).OfType<OutgoingResponse>().Select(z => z.Result)), request);
}

return await InnerRouteRequest(descriptors.Default);

async Task<ErrorResponse> InnerRouteRequest(TDescriptor descriptor)
{
using var scope = _serviceScopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<IRequestContext>();
context.Descriptor = descriptor;
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
Expand Down Expand Up @@ -132,12 +159,12 @@ public virtual async Task<ErrorResponse> RouteRequest(TDescriptor descriptor, Re

_logger.LogTrace("Response value was {Type}", responseValue?.GetType().FullName);
}

return new JsonRpc.Client.OutgoingResponse(request.Id, responseValue, request);
}
}

public abstract TDescriptor GetDescriptor(Notification notification);
public abstract TDescriptor GetDescriptor(Request request);
public abstract IRequestDescriptor<TDescriptor> GetDescriptor(Notification notification);
public abstract IRequestDescriptor<TDescriptor> GetDescriptor(Request request);

private static readonly MethodInfo SendRequestUnit = typeof(RequestRouterBase<TDescriptor>)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
Expand Down
3 changes: 2 additions & 1 deletion src/Protocol/Client/Capabilities/CodeLensCapability.cs
@@ -1,6 +1,7 @@
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;

namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities
{
public class CodeLensCapability : DynamicCapability, ConnectedCapability<ICodeLensHandler> { }
public class CodeLensCapability : DynamicCapability, ConnectedCapability<ICodeLensHandler<CanBeResolvedData>> { }
}
3 changes: 2 additions & 1 deletion src/Protocol/Client/Capabilities/CompletionCapability.cs
@@ -1,9 +1,10 @@
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;

namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities
{
public class CompletionCapability : DynamicCapability, ConnectedCapability<ICompletionHandler>
public class CompletionCapability : DynamicCapability, ConnectedCapability<ICompletionHandler<CanBeResolvedData>>
{
/// <summary>
/// The client supports the following `CompletionItem` specific
Expand Down
3 changes: 2 additions & 1 deletion src/Protocol/Client/Capabilities/DocumentLinkCapability.cs
@@ -1,9 +1,10 @@
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;

namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities
{
public class DocumentLinkCapability : DynamicCapability, ConnectedCapability<IDocumentLinkHandler>
public class DocumentLinkCapability : DynamicCapability, ConnectedCapability<IDocumentLinkHandler<CanBeResolvedData>>
{
/// <summary>
/// Whether the client support the `tooltip` property on `DocumentLink`.
Expand Down