chore: initial project import
This commit is contained in:
commit
26620707a0
347 files changed
+56172
No files matched your search
@@ -0,0 +1,115 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Fleet.Rpc;
|
||||
|
||||
/// <summary>方法路由,与传输方式无关。<typeparamref name="TContext"/> 由传输层提供(例如调用方节点)。</summary>
|
||||
public sealed class RpcRouter<TContext>
|
||||
{
|
||||
private delegate ValueTask<Result<JsonElement?>> Route(TContext context, JsonElement? parameters, CancellationToken ct);
|
||||
|
||||
private static readonly JsonElement s_emptyObject = JsonDocument.Parse("{}").RootElement.Clone();
|
||||
|
||||
private readonly Dictionary<string, Route> _routes = new(StringComparer.Ordinal);
|
||||
private readonly Action<string, Exception>? _onUnhandled;
|
||||
|
||||
public RpcRouter(Action<string, Exception>? onUnhandled = null)
|
||||
{
|
||||
_onUnhandled = onUnhandled;
|
||||
}
|
||||
|
||||
public bool Has(string method) => _routes.ContainsKey(method);
|
||||
|
||||
public IReadOnlyCollection<string> Methods => _routes.Keys;
|
||||
|
||||
public RpcRouter<TContext> On<TParams, TResult>(
|
||||
string method,
|
||||
JsonTypeInfo<TParams> paramsInfo,
|
||||
JsonTypeInfo<TResult> resultInfo,
|
||||
Func<TContext, TParams, CancellationToken, ValueTask<Result<TResult>>> handler)
|
||||
{
|
||||
_routes[method] = async (context, raw, ct) =>
|
||||
{
|
||||
if (!TryDeserialize(raw, paramsInfo, out TParams? p))
|
||||
{
|
||||
return Result<JsonElement?>.Fail(FleetErrors.InvalidParams, "params could not be parsed");
|
||||
}
|
||||
|
||||
Result<TResult> r = await handler(context, p, ct);
|
||||
return r.TryGet(out TResult? value, out FleetError? error)
|
||||
? Result<JsonElement?>.Ok(JsonSerializer.SerializeToElement(value, resultInfo))
|
||||
: Result<JsonElement?>.Fail(error);
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public RpcRouter<TContext> OnNotify<TParams>(
|
||||
string method,
|
||||
JsonTypeInfo<TParams> paramsInfo,
|
||||
Func<TContext, TParams, CancellationToken, ValueTask> handler)
|
||||
{
|
||||
_routes[method] = async (context, raw, ct) =>
|
||||
{
|
||||
if (!TryDeserialize(raw, paramsInfo, out TParams? p))
|
||||
{
|
||||
return Result<JsonElement?>.Fail(FleetErrors.InvalidParams, "params could not be parsed");
|
||||
}
|
||||
|
||||
await handler(context, p, ct);
|
||||
return Result<JsonElement?>.Ok(null);
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理一条请求或通知。请求返回响应;通知返回 null(失败时只丢弃)。
|
||||
/// 处理函数里的异常属于程序缺陷:在这个边界捕获,回复 internal 错误,并交给 <c>onUnhandled</c> 记录。
|
||||
/// </summary>
|
||||
public async ValueTask<RpcMessage?> DispatchAsync(TContext context, RpcMessage message, CancellationToken ct)
|
||||
{
|
||||
string method = message.Method ?? "";
|
||||
if (!_routes.TryGetValue(method, out Route? route))
|
||||
{
|
||||
return message.Id is null ? null : RpcMessage.Failure(message.Id, new FleetError(FleetErrors.Unsupported, $"unknown method {method}"));
|
||||
}
|
||||
|
||||
Result<JsonElement?> result;
|
||||
try
|
||||
{
|
||||
result = await route(context, message.Params, ct);
|
||||
}
|
||||
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onUnhandled?.Invoke(method, ex);
|
||||
result = Result<JsonElement?>.Fail(FleetErrors.Internal, "internal error");
|
||||
}
|
||||
|
||||
if (message.Id is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.TryGet(out JsonElement? value, out FleetError? error)
|
||||
? new RpcMessage { Id = message.Id, Result = value ?? s_emptyObject }
|
||||
: RpcMessage.Failure(message.Id, error);
|
||||
}
|
||||
|
||||
private static bool TryDeserialize<T>(JsonElement? raw, JsonTypeInfo<T> info, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out T? value)
|
||||
{
|
||||
value = default;
|
||||
try
|
||||
{
|
||||
value = raw is null ? JsonSerializer.Deserialize("{}", info) : raw.Value.Deserialize(info);
|
||||
return value is not null;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user