File: Rpc\NativeBackchannelProtocol.cs
Web Access
Project: src\src\Aspire.Hosting.Native.Server\Aspire.Hosting.Native.Server.csproj (Aspire.Hosting.Native.Server)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.Json;
using Aspire.Hosting.Native.Cli;
 
namespace Aspire.Hosting.Native.Rpc;
 
/// <summary>Parses and writes backchannel wire shapes independently of request and cursor lifetimes.</summary>
internal static class NativeBackchannelProtocol
{
    public static JsonElement NullResult { get; } =
        JsonSerializer.SerializeToElement((string?)null, NativeCliJsonContext.Default.String);
 
    public static NativeCliRequest ParseRequest(JsonElement envelope)
    {
        var request = envelope.Deserialize(NativeCliJsonContext.Default.NativeCliRequest)
            ?? throw new InvalidDataException("Invalid backchannel request.");
        if (request.Jsonrpc != "2.0" || request.Id is { ValueKind: not (JsonValueKind.String or JsonValueKind.Number) })
        {
            throw new InvalidDataException("Invalid backchannel envelope.");
        }
 
        return request;
    }
 
    public static string ReadCancellationId(NativeCliRequest request)
    {
        var cancellation = request.Params?.Deserialize(NativeCliJsonContext.Default.NativeCliCancellation)
            ?? throw new InvalidDataException("Cancellation requires a request ID.");
        if (cancellation.Id.ValueKind is not (JsonValueKind.String or JsonValueKind.Number))
        {
            throw new InvalidDataException("Cancellation requires a string or number request ID.");
        }
 
        return cancellation.Id.GetRawText();
    }
 
    public static long ReadEnumeratorToken(NativeCliRequest request)
    {
        // StreamJsonRpc enumeration: next/abort take [<integer-token>].
        // Reject nonnumbers before TryGetInt64, which throws for other kinds.
        if (request.Params is not { ValueKind: JsonValueKind.Array } arguments ||
            arguments.GetArrayLength() != 1 || arguments[0].ValueKind != JsonValueKind.Number ||
            !arguments[0].TryGetInt64(out var token))
        {
            throw new ArgumentException("An enumerator requires one integer token.");
        }
 
        return token;
    }
 
    public static JsonElement StreamDescriptor(long token) =>
        JsonSerializer.SerializeToElement(new NativeCliStreamDescriptor(token), NativeCliJsonContext.Default.NativeCliStreamDescriptor);
 
    public static JsonElement EnumeratorResult(NativeCliEnumeratorResult result) =>
        JsonSerializer.SerializeToElement(result, NativeCliJsonContext.Default.NativeCliEnumeratorResult);
 
    public static byte[] EncodeResponse(JsonElement id, JsonElement result, NativeCliError? error) =>
        JsonSerializer.SerializeToUtf8Bytes(new NativeCliResponse
        {
            Id = id,
            Result = error is null ? result : null,
            Error = error
        }, NativeCliJsonContext.Default.NativeCliResponse);
}