Skip to content

Commit

Permalink
Seperated Workspace server methods to own class
Browse files Browse the repository at this point in the history
  • Loading branch information
Somfic committed Mar 14, 2024
1 parent bd4a950 commit 29e3f21
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ public readonly struct CreateWorkspaceRequest(string name, string path) : ISocke

[JsonProperty("path")]
public string Path { get; init; } = path;

Check warning on line 11 in src-csharp/Vla/Server/Messages/Requests/CreateWorkspaceRequest.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Messages/Requests/CreateWorkspaceRequest.cs#L11

Added line #L11 was not covered by tests
}

public readonly struct WorkspaceRequest(Abstractions.Workspace workspace) : ISocketRequest
{
[JsonProperty("workspace")]
public Abstractions.Workspace Workspace { get; init; } = workspace;

Check warning on line 17 in src-csharp/Vla/Server/Messages/Requests/CreateWorkspaceRequest.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Messages/Requests/CreateWorkspaceRequest.cs#L17

Added line #L17 was not covered by tests
}
51 changes: 51 additions & 0 deletions src-csharp/Vla/Server/Methods/Workspaces.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Immutable;
using Somfic.Common;
using Vla.Server.Messages;
using Vla.Server.Messages.Requests;
using Vla.Server.Messages.Response;
using Vla.Workspace;
using WatsonWebsocket;

namespace Vla.Server.Methods;

public class Workspaces : IServerMethods
{
private readonly WorkspaceService _workspaces;

public Workspaces(WorkspaceService workspaces)
{
_workspaces = workspaces;
}

Check warning on line 18 in src-csharp/Vla/Server/Methods/Workspaces.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Methods/Workspaces.cs#L15-L18

Added lines #L15 - L18 were not covered by tests

[Request("workspaces-list-recent")]
private async Task<ISocketResponse> OnWorkspacesListRecent(ClientMetadata client)
{
var workspaces = await _workspaces.ListRecentAsync();

Check warning on line 23 in src-csharp/Vla/Server/Methods/Workspaces.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Methods/Workspaces.cs#L22-L23

Added lines #L22 - L23 were not covered by tests

return workspaces
.Map<ISocketResponse, ImmutableArray<Abstractions.Workspace>>(
w => new WorkspacesResponse(w),
e => new ExceptionResponse(e));
}

Check warning on line 29 in src-csharp/Vla/Server/Methods/Workspaces.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Methods/Workspaces.cs#L25-L29

Added lines #L25 - L29 were not covered by tests

[Request("workspace-create")]
private async Task<ISocketResponse> OnWorkspaceCreate(ClientMetadata client, CreateWorkspaceRequest request)
{
var workspace = await _workspaces.CreateOrLoadAsync(request.Name, request.Path);

Check warning on line 34 in src-csharp/Vla/Server/Methods/Workspaces.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Methods/Workspaces.cs#L33-L34

Added lines #L33 - L34 were not covered by tests

return workspace
.Map<ISocketResponse, Abstractions.Workspace>(
w => new WorkspaceResponse(w),
e => new ExceptionResponse(e));
}

Check warning on line 40 in src-csharp/Vla/Server/Methods/Workspaces.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Methods/Workspaces.cs#L36-L40

Added lines #L36 - L40 were not covered by tests

[Request("workspace-save")]
private async Task OnWorkspaceSave(ClientMetadata client, WorkspaceRequest request)
{
await _workspaces.SaveAsync(request.Workspace);
}

Check warning on line 46 in src-csharp/Vla/Server/Methods/Workspaces.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/Methods/Workspaces.cs#L44-L46

Added lines #L44 - L46 were not covered by tests
}

public interface IServerMethods
{
}
7 changes: 7 additions & 0 deletions src-csharp/Vla/Server/RequestAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Vla.Server;

[AttributeUsage(AttributeTargets.Method)]
public class RequestAttribute(string id) : Attribute

Check warning on line 4 in src-csharp/Vla/Server/RequestAttribute.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/RequestAttribute.cs#L4

Added line #L4 was not covered by tests
{
public string Id { get; } = id;

Check warning on line 6 in src-csharp/Vla/Server/RequestAttribute.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/RequestAttribute.cs#L6

Added line #L6 was not covered by tests
}
165 changes: 79 additions & 86 deletions src-csharp/Vla/Server/ServerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,110 +81,103 @@ public void OnTick(Func<Task> callback)

private async Task OnIncomingMessage(ClientMetadata client, string message)
{

Check warning on line 83 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L83

Added line #L83 was not covered by tests
var jObject = JObject.Parse(message);
var id = jObject["id"]?.Value<string>()?.ToLower() ?? string.Empty;

if (string.IsNullOrEmpty(id))
try
{
_log.LogWarning("Incoming request did not have an ID, skipping");
return;
}
var jObject = JObject.Parse(message);

Check warning on line 86 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L85-L86

Added lines #L85 - L86 were not covered by tests
var id = jObject["id"]?.Value<string>()?.ToLower() ?? string.Empty;

// TODO: Cache the available methods, since they're constant at runtime
var method = GetType()
.GetMethods()
.Select(x => (method: x, attribute: x.GetCustomAttribute(typeof(RequestAttribute))))
.Where(x => x.attribute != null)
.Select(x => (x.method, attribute: x.attribute as RequestAttribute))
.FirstOrDefault(x => x.attribute!.Id.Equals(id, StringComparison.CurrentCultureIgnoreCase));
if (string.IsNullOrEmpty(id))
{
_log.LogWarning("Incoming request did not have an ID, skipping");
return;

Check warning on line 92 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L90-L92

Added lines #L90 - L92 were not covered by tests
}

if (method.method == null)
{
_log.LogWarning("Could not find a request handler with an implementation for '{Id}, skipping'", id);
return;
}
// TODO: Cache the available methods, since they're constant at runtime
var method = GetType()
.GetMethods()
.Select(x => (method: x, attribute: x.GetCustomAttribute(typeof(RequestAttribute))))
.Where(x => x.attribute != null)
.Select(x => (x.method, attribute: x.attribute as RequestAttribute))
.FirstOrDefault(x => x.attribute!.Id.Equals(id, StringComparison.CurrentCultureIgnoreCase));

Check warning on line 101 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L96-L101

Added lines #L96 - L101 were not covered by tests

var methodParameters = method.method.GetParameters();
var invokingParameters = new dynamic[methodParameters.Length];
if (method.method == null)
{
_log.LogWarning("Could not find a request handler with an implementation for '{Id}, skipping'", id);
return;

Check warning on line 106 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L104-L106

Added lines #L104 - L106 were not covered by tests
}

for (var index = 0; index < methodParameters.Length; index++)
{
var methodParameter = methodParameters[index];
var methodParameters = method.method.GetParameters();
var invokingParameters = new dynamic[methodParameters.Length];

Check warning on line 110 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L109-L110

Added lines #L109 - L110 were not covered by tests

if (methodParameter.ParameterType == typeof(ClientMetadata))
invokingParameters[index] = client;
for (var index = 0; index < methodParameters.Length; index++)
{
var methodParameter = methodParameters[index];

Check warning on line 114 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L113-L114

Added lines #L113 - L114 were not covered by tests

if (methodParameter.ParameterType.IsSubclassOf(typeof(ISocketRequest)))
try
{
var request = JsonConvert.DeserializeObject(message, methodParameter.ParameterType);
if (methodParameter.ParameterType == typeof(ClientMetadata))
invokingParameters[index] = client;

Check warning on line 117 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L117

Added line #L117 was not covered by tests

if (request == null)
if (methodParameter.ParameterType.IsSubclassOf(typeof(ISocketRequest)))
try
{
_log.LogWarning("Could not convert request body to type {Type}", methodParameter.ParameterType);
return;
}
var request = JsonConvert.DeserializeObject(message, methodParameter.ParameterType);

Check warning on line 122 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L121-L122

Added lines #L121 - L122 were not covered by tests

invokingParameters[index] = request;
}
catch (Exception ex)
{
_log.LogWarning(ex, "Could not convert request body to type {Type}", methodParameter.ParameterType);
return;
}
}
if (request == null)
{
_log.LogWarning("Could not convert request body to type {Type}",
methodParameter.ParameterType);
return;

Check warning on line 128 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L125-L128

Added lines #L125 - L128 were not covered by tests
}

// Check if the method is async by checking if the return type is Task or Task<T>
if (method.method.ReturnType == typeof(Task))
{
await (Task)method.method.Invoke(this, invokingParameters)!;
}
// Check if the method is async and returns an ISocketMessage by checking if the return type is Task<ISocketMessage>, or Task<(type that inherits from ISocketMessage)>
else if (method.method.ReturnType.IsGenericType &&
method.method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) &&
method.method.ReturnType.GetGenericArguments().Length == 1 && method.method.ReturnType
.GetGenericArguments().First().IsAssignableTo(typeof(ISocketMessage)))
{
var result = await (Task<ISocketMessage>)method.method.Invoke(this, invokingParameters)!;
await _server.SendAsync(client, result);
invokingParameters[index] = request;
}
catch (Exception ex)
{
_log.LogWarning(ex, "Could not convert request body to type {Type}",
methodParameter.ParameterType);
return;

Check warning on line 137 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L131-L137

Added lines #L131 - L137 were not covered by tests
}
}

Check warning on line 139 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L139

Added line #L139 was not covered by tests

// If the method returns nothing, just invoke it
if (method.method.ReturnType == typeof(Task))
{
await (Task)method.method.Invoke(this, invokingParameters)!;
}

Check warning on line 145 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L143-L145

Added lines #L143 - L145 were not covered by tests
else if (method.method.ReturnType == typeof(void))
{
method.method.Invoke(this, invokingParameters);
}

Check warning on line 149 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L147-L149

Added lines #L147 - L149 were not covered by tests

// If the method returns a type that inherits from ISocketResponse, invoke it and send the result
else if (method.method.ReturnType.IsGenericType &&
method.method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) &&
method.method.ReturnType.GetGenericArguments().Length == 1 && method.method.ReturnType
.GetGenericArguments().First().IsAssignableTo(typeof(ISocketMessage)))
{
var result = await (Task<ISocketMessage>)method.method.Invoke(this, invokingParameters)!;
await _server.SendAsync(client, result);
}

Check warning on line 159 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L153-L159

Added lines #L153 - L159 were not covered by tests
else if (method.method.ReturnType.IsAssignableTo(typeof(ISocketMessage)))
{
var result = (ISocketMessage)method.method.Invoke(this, invokingParameters)!;
await _server.SendAsync(client, result);
}

Check warning on line 164 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L161-L164

Added lines #L161 - L164 were not covered by tests
else
{
_log.LogWarning("Method '{Method}' returned an unexpected type '{Type}', skipping",
method.method.Name, method.method.ReturnType);
}
}
// Else, just invoke the method and ignore the result
else
catch (Exception ex)
{
method.method.Invoke(this, invokingParameters);
_log.LogWarning(ex, "Could not process incoming message");
await _server.SendAsync(client, new ExceptionResponse(ex));
}
}

Check warning on line 176 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L166-L176

Added lines #L166 - L176 were not covered by tests

private Task OnNewClient(ClientMetadata client)
{
return Task.CompletedTask;
}

Check warning on line 181 in src-csharp/Vla/Server/ServerService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Server/ServerService.cs#L179-L181

Added lines #L179 - L181 were not covered by tests

[Request("workspaces-list-recent")]
private async Task<ISocketMessage> OnWorkspacesListRecent(ClientMetadata client)
{
var workspaces = await _workspaces.ListRecentAsync();

return workspaces
.Map<ISocketMessage, ImmutableArray<Abstractions.Workspace>>(
w => new WorkspacesResponse(w),
e => new ExceptionResponse(e));
}

[Request("workspace-create")]
private async Task<ISocketMessage> OnWorkspaceCreate(ClientMetadata client, CreateWorkspaceRequest request)
{
var workspace = await _workspaces.CreateOrLoadAsync(request.Name, request.Path);

return workspace
.Map<ISocketMessage, Abstractions.Workspace>(
w => new WorkspaceResponse(w),
e => new ExceptionResponse(e));
}

[AttributeUsage(AttributeTargets.Method)]
private class RequestAttribute(string id) : Attribute
{
public string Id { get; } = id;
}

}
12 changes: 8 additions & 4 deletions src-csharp/Vla/Websocket/WebsocketService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ public async Task StopAsync()

public async Task BroadcastAsync<TMessage>(TMessage message) where TMessage : ISocketMessage
{
_log.LogDebug("Starting broadcast of '{Message}'", message);

Check warning on line 44 in src-csharp/Vla/Websocket/WebsocketService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Websocket/WebsocketService.cs#L43-L44

Added lines #L43 - L44 were not covered by tests
foreach (var client in _server.ListClients()) await SendAsync(client, message);
_log.LogDebug("Broadcast of '{Message}' completed", message);
}

Check warning on line 47 in src-csharp/Vla/Websocket/WebsocketService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Websocket/WebsocketService.cs#L46-L47

Added lines #L46 - L47 were not covered by tests

public async Task SendAsync<TMessage>(ClientMetadata client, TMessage message) where TMessage : ISocketMessage
{
var wrappedMessage = new ServerMessage<TMessage>(message);
var json = JsonConvert.SerializeObject(wrappedMessage);
Console.WriteLine($"> {json}");
_log.LogDebug("Sending message to {Guid}: '{Message}'", client.Guid, json);
await _server.SendAsync(client.Guid, json);
}

Check warning on line 55 in src-csharp/Vla/Websocket/WebsocketService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Websocket/WebsocketService.cs#L50-L55

Added lines #L50 - L55 were not covered by tests

Expand All @@ -60,7 +62,9 @@ private async Task OnClientConnected(ConnectionEventArgs e)

private async Task OnMessageReceived(MessageReceivedEventArgs e)
{
await MessageReceived.Set((e.Client, Encoding.UTF8.GetString(e.Data.ToArray())));
var message = Encoding.UTF8.GetString(e.Data.ToArray());
_log.LogDebug("Message received from {Guid}: '{Message}'", e.Client.Guid, message);
await MessageReceived.Set((e.Client, message));
}

Check warning on line 68 in src-csharp/Vla/Websocket/WebsocketService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Websocket/WebsocketService.cs#L64-L68

Added lines #L64 - L68 were not covered by tests


Expand All @@ -71,8 +75,8 @@ public static implicit operator ServerMessage<TMessage>(TMessage message)
return new ServerMessage<TMessage>(message);
}

Check warning on line 76 in src-csharp/Vla/Websocket/WebsocketService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Websocket/WebsocketService.cs#L74-L76

Added lines #L74 - L76 were not covered by tests

[JsonProperty("type")]
public static string Type => typeof(TMessage).Name.Replace("Message", string.Empty);
[JsonProperty("id")]
public static string Id => typeof(TMessage).Name.Replace("Message", string.Empty);

Check warning on line 79 in src-csharp/Vla/Websocket/WebsocketService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Websocket/WebsocketService.cs#L79

Added line #L79 was not covered by tests

[JsonProperty("data")]
public TMessage Data { get; init; } = message;

Check warning on line 82 in src-csharp/Vla/Websocket/WebsocketService.cs

View check run for this annotation

Codecov / codecov/patch

src-csharp/Vla/Websocket/WebsocketService.cs#L82

Added line #L82 was not covered by tests
Expand Down

0 comments on commit 29e3f21

Please sign in to comment.