diff --git a/src/Fluxzy.Core/Formatters/FormatSettings.cs b/src/Fluxzy.Core/Formatters/FormatSettings.cs index b7f6fdc9..673aaaf3 100644 --- a/src/Fluxzy.Core/Formatters/FormatSettings.cs +++ b/src/Fluxzy.Core/Formatters/FormatSettings.cs @@ -20,6 +20,14 @@ public class FormatSettings public int MaxFormattableProtobufLength { get; set; } = 2 * 1024 * 1024; + /// + /// An optional custom protobuf decoder. When set, this decoder is tried first for + /// gRPC message decoding. If it returns null, the built-in protoc-based decoding + /// is used as a fallback (when is configured and + /// protoc is available on PATH), followed by raw wire-format decoding. + /// + public IProtobufDecoder? ProtobufDecoder { get; set; } + public List ProtoDirectories { get; set; } = new(); } } diff --git a/src/Fluxzy.Core/Formatters/IProtobufDecoder.cs b/src/Fluxzy.Core/Formatters/IProtobufDecoder.cs new file mode 100644 index 00000000..af8aa678 --- /dev/null +++ b/src/Fluxzy.Core/Formatters/IProtobufDecoder.cs @@ -0,0 +1,98 @@ +// Copyright 2021 - Haga Rakotoharivelo - https://github.com/haga-rak + +using System; + +namespace Fluxzy.Formatters +{ + /// + /// Decodes raw protobuf message bytes into a human-readable text representation. + /// Implement this interface to provide custom protobuf decoding + /// without requiring the protoc CLI tool on PATH. + /// + /// + /// Using a delegate: + /// + /// formatSettings.ProtobufDecoder = ProtobufDecoder.Create(context => { + /// // Your custom decoding logic here + /// return decodedText; + /// }); + /// + /// + public interface IProtobufDecoder + { + /// + /// Attempts to decode a protobuf message into a human-readable text representation. + /// + /// Context containing the raw message bytes and gRPC metadata. + /// A decoded text representation of the message, or null if decoding is not possible. + string? TryDecode(ProtobufDecodeContext context); + } + + /// + /// Provides context for decoding a single protobuf message extracted from a gRPC frame. + /// + public readonly struct ProtobufDecodeContext + { + public ProtobufDecodeContext( + ReadOnlyMemory messageData, + string? serviceName, + string? methodName, + bool isRequest) + { + MessageData = messageData; + ServiceName = serviceName; + MethodName = methodName; + IsRequest = isRequest; + } + + /// + /// The raw protobuf message bytes (without the gRPC 5-byte frame header). + /// + public ReadOnlyMemory MessageData { get; } + + /// + /// The gRPC service name extracted from the request path (e.g., "mypackage.MyService"). + /// + public string? ServiceName { get; } + + /// + /// The gRPC method name extracted from the request path (e.g., "MyMethod"). + /// + public string? MethodName { get; } + + /// + /// True if this is a request (input) message, false for a response (output) message. + /// + public bool IsRequest { get; } + } + + /// + /// Factory methods for creating instances. + /// + public static class ProtobufDecoder + { + /// + /// Creates an from a delegate. + /// + /// + /// A function that takes a and returns + /// a decoded text representation, or null if decoding is not possible. + /// + public static IProtobufDecoder Create(Func decode) + { + return new DelegateProtobufDecoder(decode ?? throw new ArgumentNullException(nameof(decode))); + } + + private class DelegateProtobufDecoder : IProtobufDecoder + { + private readonly Func _decode; + + public DelegateProtobufDecoder(Func decode) + { + _decode = decode; + } + + public string? TryDecode(ProtobufDecodeContext context) => _decode(context); + } + } +} diff --git a/src/Fluxzy.Core/Formatters/Producers/Grpc/RequestProtobufProducer.cs b/src/Fluxzy.Core/Formatters/Producers/Grpc/RequestProtobufProducer.cs index 981188e0..22c6c52d 100644 --- a/src/Fluxzy.Core/Formatters/Producers/Grpc/RequestProtobufProducer.cs +++ b/src/Fluxzy.Core/Formatters/Producers/Grpc/RequestProtobufProducer.cs @@ -46,8 +46,8 @@ internal class RequestProtobufProducer : IFormattingProducer data, ProtoFileRegistry? registry, + IProtobufDecoder? customDecoder, string? serviceName, string? methodName, bool isRequest, @@ -78,6 +79,18 @@ internal static string TryDecodeFrame( { usedDescriptor = false; + // Try custom decoder first + if (customDecoder != null) { + var context = new ProtobufDecodeContext(data, serviceName, methodName, isRequest); + var result = customDecoder.TryDecode(context); + + if (result != null) { + usedDescriptor = true; + return result; + } + } + + // Fall back to protoc-based decoding if (registry != null && serviceName != null && methodName != null) { var (input, output) = registry.FindServiceMethod(serviceName, methodName); var descriptor = isRequest ? input : output; diff --git a/src/Fluxzy.Core/Formatters/Producers/Grpc/ResponseProtobufProducer.cs b/src/Fluxzy.Core/Formatters/Producers/Grpc/ResponseProtobufProducer.cs index ec6e356c..b6b93d66 100644 --- a/src/Fluxzy.Core/Formatters/Producers/Grpc/ResponseProtobufProducer.cs +++ b/src/Fluxzy.Core/Formatters/Producers/Grpc/ResponseProtobufProducer.cs @@ -51,8 +51,8 @@ internal class ResponseProtobufProducer : IFormattingProducer