chore: initial project import

This commit is contained in:
justme committed 2026-10-04 21:46:39 +08:00
commit 26620707a0
347 files changed
+56172

No files matched your search

+115
View File
@@ -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;
}
}
}