Code/Proxy/NetworkStorageProxyEnvelope.cs

The static class defines structures for proxy response envelopes. It deserializes raw host replies, validates their format against a defined marker and version, and extracts the status and body content.

using System.Text.Json;

namespace Sandbox;

/// <summary>
/// Reply format for proxied requests. The host wraps every reply, so a requesting client can tell a relayed server
/// reply from a host failure and keeps the host's error code and message. Replies without the marker are treated as
/// raw server text from an older host.
/// </summary>
internal static class NetworkStorageProxyEnvelope
{
	internal const int Version = 1;
	internal const string EndpointKind = "endpoint";
	internal const string DocumentKind = "document";

	private const string Marker = "nsProxyEnvelope";

	/// <summary>A decoded host reply. <see cref="Body"/> holds the relayed server text when <see cref="Ok"/> is true.</summary>
	internal readonly record struct HostReply( string Kind, bool Ok, string Body, string ErrorCode, string ErrorMessage );

	internal static string Success( string kind, string body )
		=> Serialize( kind, true, body, null, null );

	internal static string Failure( string kind, string errorCode, string errorMessage )
		=> Serialize( kind, false, null, errorCode, errorMessage );

	/// <summary>Decodes a host reply. Never throws; malformed or empty replies become failures.</summary>
	internal static HostReply Read( string raw, string expectedKind )
	{
		// An empty reply means the host failed before it could answer.
		if ( string.IsNullOrWhiteSpace( raw ) )
			return Fail( expectedKind, "PROXY_FAILED", "Host returned no result." );

		JsonDocument document;
		try
		{
			document = JsonDocument.Parse( raw );
		}
		catch ( JsonException )
		{
			// Not JSON: pass it through so the client reports it the same way as any other server text.
			return new HostReply( expectedKind, true, raw, null, null );
		}

		using ( document )
		{
			var root = document.RootElement;
			if ( root.ValueKind != JsonValueKind.Object || !root.TryGetProperty( Marker, out var version ) )
				return new HostReply( expectedKind, true, raw, null, null );

			if ( version.ValueKind != JsonValueKind.Number || !version.TryGetInt32( out var number ) || number != Version )
				return Fail( expectedKind, "PROXY_FAILED", "Host replied with an unsupported proxy envelope version." );

			if ( ReadString( root, "kind" ) != expectedKind )
				return Fail( expectedKind, "PROXY_FAILED", "Host replied with an unexpected envelope kind." );

			var ok = root.TryGetProperty( "ok", out var okValue ) && okValue.ValueKind == JsonValueKind.True;
			var body = ReadString( root, "body" );
			if ( ok && body is not null )
				return new HostReply( expectedKind, true, body, null, null );

			return Fail(
				expectedKind,
				ReadString( root, "errorCode" ) ?? "PROXY_FAILED",
				ReadString( root, "errorMessage" ) ?? "Host reply failed." );
		}
	}

	private static HostReply Fail( string kind, string errorCode, string errorMessage )
		=> new( kind, false, null, errorCode, errorMessage );

	private static string ReadString( JsonElement root, string name )
		=> root.TryGetProperty( name, out var value ) && value.ValueKind == JsonValueKind.String ? value.GetString() : null;

	private static string Serialize( string kind, bool ok, string body, string errorCode, string errorMessage )
		=> JsonSerializer.Serialize( new { nsProxyEnvelope = Version, kind, ok, body, errorCode, errorMessage } );
}