Skip to content

Pluggable protocol codecs#413

Merged
leynos merged 29 commits intomainfrom
pluggable-protocol-codecs
Jan 2, 2026
Merged

Pluggable protocol codecs#413
leynos merged 29 commits intomainfrom
pluggable-protocol-codecs

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Dec 30, 2025

Summary by Sourcery

Introduce a pluggable FrameCodec abstraction and make WireframeApp/server runtime generic over protocol-specific codecs while preserving a length-delimited default.

New Features:

  • Add a FrameCodec trait with a default LengthDelimitedFrameCodec implementation and expose it from the crate API.
  • Enable WireframeApp and WireframeServer to be configured with custom frame codecs and provide new helpers for sending responses over arbitrary framed streams.
  • Add example applications and integration tests demonstrating custom protocol codecs such as Hotline, MySQL, and a tagged test codec.

Enhancements:

  • Align fragmentation configuration and buffer sizing with codec-defined maximum frame lengths and clamp frame sizes to guarded limits.
  • Improve deserialization error handling by logging correlation IDs when available and resetting fragmentation defaults when codecs change.
  • Refactor internal frame handling and response forwarding to operate on generic frame types instead of raw byte buffers.

Build:

  • Update the Makefile and markdown linting configuration to use markdownlint-cli2 and simplify markdown and Mermaid validation commands.

Documentation:

  • Extend the documentation style guide with roadmap and ADR conventions and add ADRs and an execution plan describing pluggable codecs and serializer abstraction.
  • Update the user guide and roadmap to document codec configuration, fragmentation behaviour, and follow-up hardening work.

Tests:

  • Add unit tests for the default length-delimited codec and integration tests covering custom FrameCodec round-trips and behaviour.

Document the pluggable FrameCodec decision and outline\nexecution steps for implementing protocol-specific framing.
Introduce a FrameCodec trait with a length-delimited adapter and\nthread codec selection through WireframeApp and WireframeServer.\n\nUpdate connection handling to use codec-provided decoders/encoders, add\nframed send helpers, and refresh ADR/ExecPlan/user guide documentation.
Add Hotline/MySQL codec examples showing custom framing headers and\nintroduce a TaggedFrame integration test that exercises a custom FrameCodec.\nUpdate the ExecPlan to record completion.
- Add Phase 7.6 codec hardening tasks to the roadmap.

- Capture implementation findings and open questions in ADR 004.

- Record tooling updates for Nixie and markdownlint.
- Document options to decouple message encoding from bincode.

- Link ADR 005 in the codec hardening roadmap.
- Require the examples feature for Hotline and MySQL codecs.
- Require the examples feature for async_stream, metadata_routing, and multi_packet.
Clamp frame lengths to 64B-16MiB guardrails, warning when
codecs request larger sizes, and reserve only the clamped size for
reads.

When decoded envelopes lack correlation IDs, merge header-derived
correlation IDs so header-based codecs can round-trip IDs.

Update codec tests to match the new clamp behaviour.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 30, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • New Features

    • Pluggable protocol codec system with runtime-swappable codecs and a with_codec() builder.
    • Serializer abstraction allowing alternate serializers beyond the default.
    • Added ready-to-run examples for Hotline, MySQL and RESP codecs.
  • Documentation

    • New ADRs, execplan and roadmap updates describing codec and serializer designs.
    • Updated user guide with framing, fragmentation and codec/serializer configuration.
  • Tests

    • New unit and integration tests exercising codec round-trips and backoff behaviour.
  • Chores

    • Tooling/Makefile updates and markdown linter configuration expanded.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Introduce a generic FrameCodec trait with a default LengthDelimitedFrameCodec; parameterise WireframeApp, server, runtime and connection handling over codecs; thread codec behaviour through framing, encoding/decoding and response paths; add CombinedCodec, lifecycle and middleware types; add example codecs (Hotline, MySQL, RESP), RESP parser/encoder, tests, ADRs, execplan, roadmap and tooling updates.

Changes

Cohort / File(s) Summary
Configuration & Manifest
\.markdownlint-cli2.jsonc, Makefile, Cargo.toml
Expand markdownlint ignores; switch MDLINT to markdownlint-cli2; add itoa dependency; register multiple [[example]] and [[test]] entries in Cargo.toml.
Core Codec Framework
src/codec.rs, src/lib.rs, src/codec/examples.rs
Add FrameCodec trait and default LengthDelimitedFrameCodec with decoder/encoder adapters, clamp helpers and constants; implement example codecs (Hotline, MySQL); re-export codec types in lib.rs.
Application Builder
src/app/builder.rs, src/app/builder_defaults.rs, src/app/mod.rs
Parameterise WireframeApp over F: FrameCodec; replace buffer_capacity with codec: F; add with_codec, rebuild_with_params and specialised buffer_capacity() for LengthDelimitedFrameCodec; derive fragmentation from codec.max_frame_length(); add builder defaults/helpers.
Connection & Framing
src/app/connection.rs, src/app/combined_codec.rs, src/app/frame_handling.rs
Thread F: FrameCodec through connection and frame handling; add CombinedCodec + ConnectionCodec alias; generalise send paths (send_response_framed_with_codec, specialised send_response_framed); switch to payload-based send flow using F::wrap_payload() / F::frame_payload(); propagate correlation via F::correlation_id().
App types & Middleware
src/app/lifecycle.rs, src/app/middleware_types.rs, src/app/mod.rs
Add ConnectionSetup/ConnectionTeardown lifecycle aliases; add Handler and Middleware abstractions with blanket impl; reorganise modules and re-export new types.
Server Config & Runtime
src/server/mod.rs, src/server/config/*, src/server/connection.rs, src/server/runtime.rs, src/server/runtime/backoff.rs, src/server/runtime/tests.rs
Propagate generics Ser, Ctx, E, Codec across WireframeServer, factory traits and impls; tighten bounds (Serializer/Packet/FrameCodec); add ConnectionApp trait and generic wiring for spawn/process_stream; add BackoffConfig and runtime tests; update runtime/accept-loop signatures to include codec generics.
Examples & RESP module
examples/hotline_codec.rs, examples/mysql_codec.rs, examples/resp_codec.rs, examples/resp_codec_impl/*
Add Hotline, MySQL and RESP framing examples; add RESP frame type, parser, encoder and RespFrameCodec/RespAdapter implementation with encode/parse helpers using itoa.
Tests & Example Codecs
tests/frame_codec.rs, tests/example_codecs.rs, tests/wireframe_protocol.rs, src/app/connection/tests.rs, src/server/runtime/tests.rs
Add unit/integration tests for TaggedFrame/Hotline/Mysql/RESP codecs, VecFrameCodec for tests, decode-failure correlation logging, backoff, accept-loop and shutdown behaviours.
New App internals
src/app/combined_codec.rs, src/app/builder_defaults.rs, src/app/lifecycle.rs, src/app/middleware_types.rs
Add CombinedCodec wrapper, default_fragmentation helpers and read-timeout constants, lifecycle hook types and middleware abstractions.
Docs & Planning
docs/adr-004-pluggable-protocol-codecs.md, docs/adr-005-serializer-abstraction.md, docs/execplans/pluggable-protocol-codecs.md, docs/documentation-style-guide.md, docs/roadmap.md, docs/users-guide.md
Add ADRs, execplan, roadmap entries and documentation updates covering codec/serializer design, migration path, user guide changes (default length-delimited framing, send helpers, fragmentation derived from codec).

Sequence Diagram(s)

%%{init: {"themeVariables":{"noteBackground":"#eef7ee","actorBorder":"#2b7a2b","sequenceNumber":"#6b6b6b"},"theme":"default"}}%%
sequenceDiagram
    autonumber
    participant Client as TCP Transport
    participant Codec as FrameCodec (F)
    participant App as WireframeApp<S,Ctx,E,F>
    participant Handler as Protocol Handler

    rect rgb(235,247,235)
    Note over Client,Codec: Inbound — read bytes → decode → deliver frame
    Client->>Codec: Read bytes
    Codec->>Codec: decoder.decode()
    Codec-->>App: F::Frame
    App->>App: payload = F::frame_payload(frame)
    App->>App: deserialize payload -> Envelope
    App->>App: corr = F::correlation_id(frame)
    App->>Handler: Route Envelope
    Handler-->>App: Response Envelope
    end

    rect rgb(250,235,235)
    Note over App,Codec: Outbound — serialize → wrap → encode → write
    App->>App: serialize response -> payload
    App->>Codec: frame = F::wrap_payload(payload)
    Codec->>Codec: encoder.encode(frame)
    Codec->>Client: Write bytes
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

"Frame trait wakes, codecs conspire,
Packets march through encoder fire,
Hotline, MySQL, RESP take part,
ADRs, tests and docs depart,
Servers backoff — let integration sing 🎉"

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Pluggable protocol codecs' directly and specifically describes the main changeset's primary feature—introducing a pluggable FrameCodec abstraction. The title is clear and meaningful.
Description check ✅ Passed The description comprehensively details the changeset across multiple dimensions: new trait/features, example codecs, documentation updates, build changes, and test additions. It is directly related to the actual changes and conveys meaningful information about the work.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pluggable-protocol-codecs

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 30, 2025

Reviewer's Guide

Make framing pluggable by introducing a FrameCodec trait with a default length-delimited implementation, threading the codec type through WireframeApp and WireframeServer, aligning fragmentation and buffering with codec frame limits, improving correlation-aware deserialization logging, and adding documentation, examples, tests, and tooling updates around codecs and roadmap/ADR conventions.

Sequence diagram for connection handling with pluggable FrameCodec

sequenceDiagram
    actor Client
    participant TcpListener
    participant WireframeServer
    participant Connection as ConnectionTask
    participant WireframeApp
    participant FrameCodecImpl as FrameCodec
    participant FramedStream as Framed~W,C~
    participant Serializer
    participant HandlerService

    Client->>TcpListener: TCP connect
    TcpListener->>WireframeServer: incoming connection
    WireframeServer->>Connection: spawn_connection_task(stream, factory, hooks)
    Connection->>WireframeApp: factory()
    WireframeApp-->>Connection: app instance (with codec F)

    Connection->>FrameCodecImpl: decoder() / encoder()
    FrameCodecImpl-->>Connection: Decoder, Encoder
    Connection->>FramedStream: Framed::new(stream, CombinedCodec)

    loop per frame
        FramedStream->>Connection: next() -> Frame
        Connection->>WireframeApp: handle_frame(frame, FrameHandlingContext)
        WireframeApp->>FrameCodecImpl: frame_payload(frame)
        FrameCodecImpl-->>WireframeApp: payload bytes
        WireframeApp->>Serializer: parse(payload) or deserialize(payload)
        Serializer-->>WireframeApp: Envelope or error
        alt deserialization ok
            WireframeApp->>HandlerService: call(ServiceRequest)
            HandlerService-->>WireframeApp: Response
            WireframeApp->>Serializer: serialize(Response)
            Serializer-->>WireframeApp: payload bytes
            WireframeApp->>FrameCodecImpl: wrap_payload(payload)
            FrameCodecImpl-->>WireframeApp: Frame
            WireframeApp->>FramedStream: send(frame)
            FramedStream->>Client: write bytes
        else deserialization error
            WireframeApp->>FrameCodecImpl: correlation_id(frame)
            FrameCodecImpl-->>WireframeApp: correlation_id?
            WireframeApp->>WireframeApp: handle_decode_failure(correlation_id)
            Note over WireframeApp: increment deser_failures, maybe close connection
        end
    end
Loading

Class diagram for FrameCodec trait and default/ example codec implementations

classDiagram
    class FrameCodec {
        <<trait>>
        +type Frame
        +decoder() Decoder~Frame~
        +encoder() Encoder~Frame~
        +frame_payload(frame: Frame) byte[]
        +wrap_payload(payload: byte[]) Frame
        +correlation_id(frame: Frame) u64?
        +max_frame_length() usize
    }

    class LengthDelimitedFrameCodec {
        +max_frame_length: usize
        +new(max_frame_length: usize) LengthDelimitedFrameCodec
        +max_frame_length() usize
    }

    class LengthDelimitedAdapter {
        -inner: LengthDelimitedCodec
        +new(max_frame_length: usize) LengthDelimitedAdapter
        +decode(src: BytesMut) Vec~u8~?
        +decode_eof(src: BytesMut) Vec~u8~?
        +encode(item: Vec~u8~, dst: BytesMut)
    }

    class HotlineFrameCodec {
        +max_frame_length: usize
        +new(max_frame_length: usize) HotlineFrameCodec
        +decoder() HotlineAdapter
        +encoder() HotlineAdapter
        +frame_payload(frame: HotlineFrame) byte[]
        +wrap_payload(payload: byte[]) HotlineFrame
        +correlation_id(frame: HotlineFrame) u64?
        +max_frame_length() usize
    }

    class HotlineAdapter {
        +max_frame_length: usize
        +new(max_frame_length: usize) HotlineAdapter
        +decode(src: BytesMut) HotlineFrame?
        +encode(item: HotlineFrame, dst: BytesMut)
    }

    class HotlineFrame {
        +transaction_id: u32
        +payload: Vec~u8~
    }

    class MysqlFrameCodec {
        +max_frame_length: usize
        +new(max_frame_length: usize) MysqlFrameCodec
        +decoder() MysqlAdapter
        +encoder() MysqlAdapter
        +frame_payload(frame: MysqlFrame) byte[]
        +wrap_payload(payload: byte[]) MysqlFrame
        +correlation_id(frame: MysqlFrame) u64?
        +max_frame_length() usize
    }

    class MysqlAdapter {
        +max_frame_length: usize
        +new(max_frame_length: usize) MysqlAdapter
        +decode(src: BytesMut) MysqlFrame?
        +encode(item: MysqlFrame, dst: BytesMut)
    }

    class MysqlFrame {
        +sequence_id: u8
        +payload: Vec~u8~
    }

    class CombinedCodec {
        +decoder: Decoder
        +encoder: Encoder
        +new(decoder: Decoder, encoder: Encoder) CombinedCodec
        +decode(src: BytesMut) Item?
        +decode_eof(src: BytesMut) Item?
        +encode(item: Item, dst: BytesMut)
    }

    FrameCodec <|.. LengthDelimitedFrameCodec
    FrameCodec <|.. HotlineFrameCodec
    FrameCodec <|.. MysqlFrameCodec

    LengthDelimitedFrameCodec ..> LengthDelimitedAdapter : uses
    HotlineFrameCodec ..> HotlineAdapter : uses
    MysqlFrameCodec ..> MysqlAdapter : uses

    HotlineAdapter --> HotlineFrame : decodes
    MysqlAdapter --> MysqlFrame : decodes

    CombinedCodec ..> Decoder : composes
    CombinedCodec ..> Encoder : composes

    class Decoder {
        <<trait>>
    }

    class Encoder {
        <<trait>>
    }
Loading

Class diagram for WireframeApp and WireframeServer with pluggable FrameCodec

classDiagram
    class WireframeApp {
        <<generic S, Ctx, E, F>>
        +handlers: HashMap~u32, Handler~E~~
        +routes: OnceCell~Arc~HashMap~u32, HandlerService~E~~~~
        +middleware: Vec~Arc~dyn Middleware~E~~
        +serializer: S
        +app_data: HashMap~TypeId, Arc~dyn Any~~
        +on_connect: Option~Arc~ConnectionSetup~Ctx~~
        +on_disconnect: Option~Arc~ConnectionTeardown~Ctx~~
        +protocol: Option~Arc~dyn WireframeProtocol~Frame = F::Frame~~
        +push_dlq: Option~mpsc::Sender~Vec~u8~~
        +codec: F
        +read_timeout_ms: u64
        +fragmentation: Option~FragmentationConfig~
        +new() WireframeApp
        +with_codec(codec: F2) WireframeApp~S, Ctx, E, F2~
        +serializer(serializer: S2) WireframeApp~S2, Ctx, E, F~
        +buffer_capacity(capacity: usize) WireframeApp~S, Ctx, E, LengthDelimitedFrameCodec~
        +protocol() WireframeProtocol~Frame = F::Frame~?
        +protocol_hooks() ProtocolHooks~F::Frame, ()~
        +send_response(stream, msg)
        +send_response_framed_with_codec(framed, msg)
        +handle_connection(stream)
    }

    class WireframeServer {
        <<generic FFactory, PreambleT, State, Ser, Ctx, E, Codec>>
        +factory: FFactory
        +workers: usize
        +ready_tx: Option~oneshot::Sender~()~~
        +ready_rx: ReadyState
        +backoff_config: BackoffConfig
        +preamble_timeout: Option~Duration~
        +state: State
        +_app: PhantomData~(Ser, Ctx, E, Codec)~
        +_preamble: PhantomData~PreambleT~
        +new(factory: FFactory) WireframeServer
        +with_preamble() WireframeServer~FFactory, P, State, Ser, Ctx, E, Codec~
        +bind(addr: SocketAddr) WireframeServer~FFactory, PreambleT, Bound, Ser, Ctx, E, Codec~
        +run()
    }

    class FrameHandlingContext {
        <<generic E, W, F, C>>
        +framed: Framed~W, C~
        +deser_failures: u32*
        +routes: HashMap~u32, HandlerService~E~~
        +fragmentation: Option~FragmentationState~
        +codec_marker: PhantomData~F~
    }

    class ResponseContext {
        <<generic S, W, F, C>>
        +serializer: S*
        +framed: Framed~W, C~
        +fragmentation: Option~FragmentationState~
        +codec_marker: PhantomData~F~
    }

    class FrameCodec_Trait {
        <<alias FrameCodec>>
    }

    class Serializer_Trait {
        <<alias Serializer>>
    }

    class Packet_Trait {
        <<alias Packet>>
    }

    class Preamble_Trait {
        <<alias Preamble>>
    }

    class FragmentationConfig {
    }

    class FragmentationState {
        +new(cfg: FragmentationConfig) FragmentationState
    }

    WireframeApp --> FrameCodec_Trait : codec F FrameCodec
    WireframeApp --> Serializer_Trait : S Serializer
    WireframeApp --> Packet_Trait : E Packet
    WireframeApp --> FragmentationConfig : uses
    WireframeApp --> FrameHandlingContext : builds
    WireframeApp --> ResponseContext : builds

    WireframeServer --> WireframeApp : factory returns
    WireframeServer --> Preamble_Trait : T Preamble

    FrameHandlingContext --> FragmentationState : manages
    ResponseContext --> FragmentationState : manages
Loading

File-Level Changes

Change Details Files
Introduce a pluggable FrameCodec abstraction with a default LengthDelimitedFrameCodec and integrate it into WireframeApp framing, fragmentation, and response sending paths.
  • Add src/codec.rs defining the FrameCodec trait, frame length clamp helpers, and LengthDelimitedFrameCodec implemented via a LengthDelimitedAdapter over tokio_util::LengthDelimitedCodec.
  • Make WireframeApp generic over a FrameCodec type parameter with a default LengthDelimitedFrameCodec, store the codec in the app, and provide a with_codec builder that rebuilds the app and resets fragmentation based on codec.max_frame_length().
  • Replace direct LengthDelimitedCodec usage in app connection handling with codec-provided encoder/decoder by introducing a CombinedCodec wrapper, parameterizing FrameHandlingContext/ResponseContext over codec types, using FrameCodec::wrap_payload and FrameCodec::frame_payload for IO, and adding send_response_framed_with_codec for framed streams using custom codecs.
  • Align fragmentation configuration with codec frame limits by deriving default FragmentationConfig from codec.max_frame_length via default_fragmentation and updating buffer_capacity to reconfigure the default LengthDelimitedFrameCodec instead of a raw buffer size.
src/codec.rs
src/app/builder.rs
src/app/connection.rs
src/app/frame_handling.rs
Propagate codec, serializer, and packet type parameters through WireframeServer, its configuration, and runtime accept/connection paths to support typed codecs end-to-end.
  • Generalize WireframeServer to carry serializer, context, packet, and codec type parameters with defaults, including PhantomData markers for the app types.
  • Refine WireframeFactory and WireframeServer impl bounds in server/config/binding.rs and server/config/mod.rs to be generic over Serializer, context, packet, and FrameCodec.
  • Update server runtime accept_loop, accept_iteration, and process_stream signatures to accept WireframeApp<Ser, Ctx, E, Codec> factories and bound Ser: Serializer + FrameMetadata<Frame = Envelope>, E: Packet, Codec: FrameCodec.
  • Fix server tests and examples to use the updated generic signatures, including explicit type parameters where inference would fail.
src/server/mod.rs
src/server/config/mod.rs
src/server/config/binding.rs
src/server/config/preamble.rs
src/server/runtime.rs
src/server/connection.rs
Improve deserialization failure handling by deriving correlation IDs from codec frames and logging them, and ensure codec-aware fragmentation and response forwarding semantics.
  • Change decode_envelope to operate on F::Frame, extract payload via FrameCodec::frame_payload, and populate Envelope.correlation_id from FrameCodec::correlation_id when missing.
  • Update handle_decode_failure to accept an Option correlation_id and log it directly instead of always logging None.
  • Thread PhantomData through FrameHandlingContext and ResponseContext so frame-type-specific information can be tracked without storing a codec instance.
  • Adjust forward_response and send_response_payload to construct frames via F::wrap_payload and send them through the framed codec stream, preserving correlation and fragmentation semantics.
src/app/connection.rs
src/app/frame_handling.rs
Add protocol-specific codec examples and integration tests to demonstrate custom FrameCodec usage and validate round-trip behavior.
  • Add Hotline and MySQL example codecs implementing FrameCodec with protocol-aware frame structs, adapter encoder/decoder pairs, and correlation_id extraction based on transaction/sequence IDs, wiring them into WireframeApp instances in examples/hotline_codec.rs and examples/mysql_codec.rs.
  • Add an integration test tests/frame_codec.rs defining a TaggedFrame/TaggedFrameCodec, exercising FrameCodec round-tripping through WireframeApp::handle_connection_result using a duplex stream, and asserting that correlation IDs are surfaced from frame tags.
  • Register new examples (async_stream, metadata_routing, multi_packet, hotline_codec, mysql_codec) in Cargo.toml under the examples feature for compilation.
  • Ensure codec examples serialize Envelopes with BincodeSerializer and configure simple routes to make the examples compile and run.
examples/hotline_codec.rs
examples/mysql_codec.rs
tests/frame_codec.rs
Cargo.toml
Extend documentation and project tooling around roadmap, ADRs, codec plans, and Markdown/Mermaid linting.
  • Update the documentation style guide with roadmap formatting conventions and a full ADR authoring template, covering required/conditional sections and formatting guidance.
  • Add ADR 004 describing the pluggable protocol codecs design and implementation details, and ADR 005 proposing a future serializer abstraction beyond bincode.
  • Extend the roadmap with Phase 7.6 tasks for codec hardening, fragmentation adapter alignment, unified codec handling, property-based tests, serializer boundaries, and performance benchmarks, plus a note about codec metadata usage for version negotiation.
  • Add an execution plan document for pluggable protocol codecs capturing purpose, validation commands, implementation steps, and a running decision log.
  • Update the user guide to describe codec configuration, defaults, send helpers (including send_response_framed_with_codec), and fragmentation behaviour in terms of codec frame limits instead of buffer_capacity.
  • Switch Markdown linting to markdownlint-cli2 and adjust nixie invocation to use a simpler CLI in the Makefile, and add a markdownlint-cli2 configuration file stub.
docs/documentation-style-guide.md
docs/roadmap.md
docs/users-guide.md
docs/adr-004-pluggable-protocol-codecs.md
docs/adr-005-serializer-abstraction.md
docs/execplans/pluggable-protocol-codecs.md
Makefile
.markdownlint-cli2.jsonc

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 30, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/app/builder.rs

Comment on file

    /// Replace the serializer used for messages.
    #[must_use]
    pub fn serializer<Ser>(self, serializer: Ser) -> WireframeApp<Ser, C, E>
    pub fn serializer<Ser>(self, serializer: Ser) -> WireframeApp<Ser, C, E, F>

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: WireframeApp.serializer,WireframeApp.with_codec

@coderabbitai

This comment was marked as resolved.

Introduce a single rebuild helper for serializer and codec
transitions, using  to avoid unnecessary clones.

Explicitly document the clippy exception for the helper's
argument list.
@leynos leynos marked this pull request as ready for review December 30, 2025 16:26
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 16 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/app/connection.rs:76` </location>
<code_context>
+
 /// Per-frame processing state bundled for `handle_frame`.
-struct FrameHandlingContext<'a, E, W>
+struct FrameHandlingContext<'a, E, W, F, C>
 where
     E: Packet,
</code_context>

<issue_to_address>
**issue (complexity):** Consider simplifying the connection handling generics by fixing `FrameHandlingContext` and `handle_frame` to the concrete connection codec instead of carrying extra type parameters and `PhantomData`.

You can keep the new `FrameCodec` abstraction but significantly reduce generic / phantom complexity around `handle_frame` and the context types by tying them to the concrete connection codec you actually use.

### 1. Collapse `FrameHandlingContext` generics and drop `PhantomData`

Right now:

```rust
struct FrameHandlingContext<'a, E, W, F, C>
where
    E: Packet,
    W: AsyncRead + AsyncWrite + Unpin,
    F: FrameCodec,
    C: Encoder<F::Frame, Error = io::Error>,
{
    framed: &'a mut Framed<W, C>,
    deser_failures: &'a mut u32,
    routes: &'a HashMap<u32, HandlerService<E>>,
    fragmentation: &'a mut Option<FragmentationState>,
    codec_marker: PhantomData<F>,
}
```

You only ever construct this with `CombinedCodec::new(self.codec.decoder(), self.codec.encoder())`. You can encode that directly in the type and remove `C` and the `codec_marker`:

```rust
type ConnectionCodec<F> =
    CombinedCodec<<F as FrameCodec>::Decoder, <F as FrameCodec>::Encoder>;

struct FrameHandlingContext<'a, E, W, F>
where
    E: Packet,
    W: AsyncRead + AsyncWrite + Unpin,
    F: FrameCodec,
{
    framed: &'a mut Framed<W, ConnectionCodec<F>>,
    deser_failures: &'a mut u32,
    routes: &'a HashMap<u32, HandlerService<E>>,
    fragmentation: &'a mut Option<FragmentationState>,
}
```

Use it in `process_stream`:

```rust
let codec = CombinedCodec::new(self.codec.decoder(), self.codec.encoder());
let mut framed = Framed::new(stream, codec);
// ...
self.handle_frame(
    &frame,
    FrameHandlingContext {
        framed: &mut framed,
        deser_failures: &mut deser_failures,
        routes,
        fragmentation: &mut fragmentation,
    },
)
.await?;
```

### 2. Simplify `handle_frame` signature

You don’t need to parameterise `handle_frame` over an arbitrary encoder type `Cc` — you always use the app’s codec for this connection:

```rust
async fn handle_frame<W>(
    &self,
    frame: &F::Frame,
    ctx: FrameHandlingContext<'_, E, W, F>,
) -> io::Result<()>
where
    W: AsyncRead + AsyncWrite + Unpin,
{
    let FrameHandlingContext {
        framed,
        deser_failures,
        routes,
        fragmentation,
    } = ctx;

    // existing logic
}
```

This keeps `send_response_framed_with_codec` generic over arbitrary `Cc` (for callers that want a custom encoder) while making the core connection handling path (`process_stream` / `handle_frame`) much easier to read: one fewer generic parameter on the context, no `PhantomData`, and one less type parameter on `handle_frame`.
</issue_to_address>

### Comment 2
<location> `src/app/builder.rs:196` </location>
<code_context>
+        clippy::too_many_arguments,
+        reason = "Rebuild helper maps fields explicitly when swapping serializer/codec types."
+    )]
+    fn rebuild_with_params<S2, F2>(
+        self,
+        serializer: S2,
</code_context>

<issue_to_address>
**issue (complexity):** Consider removing the generic `rebuild_with_params` helper and instead inline direct struct reconstruction in `with_codec` and `serializer` to simplify the type bounds and implementation surface.

You can drop `rebuild_with_params` (and the `Default` bounds it introduces) by reconstructing the struct directly in the few places where the type parameters change. That removes a generic helper with many parameters and simplifies the impl surface.

Two focused changes:

1. **Inline `with_codec` without `S: Default`**

```rust
impl<S, C, E, F> WireframeApp<S, C, E, F>
where
    S: Serializer + Send + Sync,
    C: Send + 'static,
    E: Packet,
    F: FrameCodec,
{
    #[must_use]
    pub fn with_codec<F2: FrameCodec>(self, codec: F2) -> WireframeApp<S, C, E, F2> {
        let fragmentation = default_fragmentation(codec.max_frame_length());

        let WireframeApp {
            handlers,
            routes: _,
            middleware,
            serializer,
            app_data,
            on_connect,
            on_disconnect,
            protocol: _,
            push_dlq,
            codec: _,
            read_timeout_ms,
            fragmentation: _,
        } = self;

        WireframeApp {
            handlers,
            routes: OnceCell::new(),
            middleware,
            serializer,
            app_data,
            on_connect,
            on_disconnect,
            protocol: None, // protocol frame type changed
            push_dlq,
            codec,
            read_timeout_ms,
            fragmentation,
        }
    }
}
```

This:

- Removes the `S: Default` requirement.
- Keeps the semantics: routes reset, protocol cleared, fragmentation recomputed.

2. **Inline `serializer` without `F: Default` and without `rebuild_with_params`**

```rust
impl<S, C, E, F> WireframeApp<S, C, E, F>
where
    S: Serializer + Send + Sync,
    C: Send + 'static,
    E: Packet,
    F: FrameCodec,
{
    #[must_use]
    pub fn serializer<Ser>(self, serializer: Ser) -> WireframeApp<Ser, C, E, F>
    where
        Ser: Serializer + Send + Sync,
    {
        let WireframeApp {
            handlers,
            routes: _,
            middleware,
            serializer: _,
            app_data,
            on_connect,
            on_disconnect,
            protocol,
            push_dlq,
            codec,
            read_timeout_ms,
            fragmentation,
        } = self;

        WireframeApp {
            handlers,
            routes: OnceCell::new(),
            middleware,
            serializer,
            app_data,
            on_connect,
            on_disconnect,
            protocol,
            push_dlq,
            codec,
            read_timeout_ms,
            fragmentation,
        }
    }
}
```

This:

- Removes the `F: Default` bound.
- Keeps all existing behaviour (routes reset; codec, protocol, fragmentation preserved).

With these two changes, `rebuild_with_params` becomes unnecessary and can be removed. That reduces the generic plumbing and avoids the `clippy::too_many_arguments` exemption, while leaving the public API and semantics intact.
</issue_to_address>

### Comment 3
<location> `src/server/config/binding.rs:20` </location>
<code_context>
 /// Helper trait alias for wireframe factory functions
-trait WireframeFactory: Fn() -> WireframeApp + Send + Sync + Clone + 'static {}
-impl<F> WireframeFactory for F where F: Fn() -> WireframeApp + Send + Sync + Clone + 'static {}
+trait WireframeFactory<Ser, Ctx, E, Codec>:
+    Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static
+where
</code_context>

<issue_to_address>
**issue (complexity):** Consider simplifying the generic surface by removing redundant bounds from `WireframeFactory` and optionally collapsing the app generics on `WireframeServer` into a single app-type parameter with associated types.

You can trim a lot of the generic noise without changing behavior by:

1. **Removing redundant bounds from `WireframeFactory`**  
2. **Hiding the app type parameters behind a single app-type parameter on `WireframeServer`** (optional but high impact).

---

### 1. Simplify `WireframeFactory` (no Ser/Ctx/E/Codec bounds here)

Right now the factory trait repeats the same bounds you already state on `WireframeServer` impls:

```rust
trait WireframeFactory<Ser, Ctx, E, Codec>:
    Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static
where
    Ser: Serializer + Send + Sync,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{
}

impl<F, Ser, Ctx, E, Codec> WireframeFactory<Ser, Ctx, E, Codec> for F
where
    F: Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static,
    Ser: Serializer + Send + Sync,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{
}
```

You can rely on the blanket `impl` for `WireframeServer` to enforce the Ser/Ctx/E/Codec constraints and keep the factory trait minimal:

```rust
trait WireframeFactory<Ser, Ctx, E, Codec>:
    Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static
{
}

impl<F, Ser, Ctx, E, Codec> WireframeFactory<Ser, Ctx, E, Codec> for F
where
    F: Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static,
{
}
```

Then your `WireframeServer` blanket impls keep the actual constraints in one place:

```rust
impl<F, T, S, Ser, Ctx, E, Codec> WireframeServer<F, T, S, Ser, Ctx, E, Codec>
where
    F: WireframeFactory<Ser, Ctx, E, Codec>,
    T: WireframePreamble,
    S: ServerState,
    Ser: Serializer + Send + Sync,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{
    // ...
}
```

This removes duplicated where-clauses without changing the type surface.

---

### 2. Collapse app generics into a single type parameter on `WireframeServer` (bigger but cleaner)

If you want to substantially reduce the generic lists in `binding.rs`, you can wrap the app-related generics into an app-type parameter with associated types.

Example pattern (names adjusted to your domain):

```rust
pub trait WireframeAppTypes {
    type Ser: Serializer + Send + Sync;
    type Ctx: Send + 'static;
    type Packet: Packet;
    type Codec: FrameCodec;
}

impl<Ser, Ctx, E, Codec> WireframeAppTypes for WireframeApp<Ser, Ctx, E, Codec>
where
    Ser: Serializer + Send + Sync,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{
    type Ser = Ser;
    type Ctx = Ctx;
    type Packet = E;
    type Codec = Codec;
}
```

Then redefine `WireframeServer` to be generic over a single app type `A` instead of the four individual parameters:

```rust
pub struct WireframeServer<F, T, S, A>
where
    A: WireframeAppTypes,
{
    factory: F,
    // ...
    state: S,
    _app: std::marker::PhantomData<A>,
    _preamble: std::marker::PhantomData<T>,
}
```

Your helper alias and impls in `binding.rs` then shrink to:

```rust
type BoundServer<F, T, A> = WireframeServer<F, T, Bound, A>;

trait WireframeFactory<A>: Fn() -> A + Send + Sync + Clone + 'static
where
    A: WireframeAppTypes,
{
}

impl<F, A> WireframeFactory<A> for F
where
    F: Fn() -> A + Send + Sync + Clone + 'static,
    A: WireframeAppTypes,
{
}

impl<F, T, S, A> WireframeServer<F, T, S, A>
where
    F: WireframeFactory<A>,
    T: WireframePreamble,
    S: ServerState,
    A: WireframeAppTypes,
{
    fn bind_to_listener(
        self,
        std_listener: StdTcpListener,
    ) -> Result<BoundServer<F, T, A>, ServerError> {
        let WireframeServer {
            factory,
            workers,
            on_preamble_success,
            on_preamble_failure,
            ready_tx,
            backoff_config,
            preamble_timeout,
            _app: app_marker,
            _preamble: preamble_marker,
            ..
        } = self;

        std_listener.set_nonblocking(true).map_err(ServerError::Bind)?;
        let tokio_listener = TcpListener::from_std(std_listener).map_err(ServerError::Bind)?;

        Ok(WireframeServer {
            factory,
            workers,
            on_preamble_success,
            on_preamble_failure,
            ready_tx,
            backoff_config,
            preamble_timeout,
            state: Bound {
                listener: Arc::new(tokio_listener),
            },
            _app: app_marker,
            _preamble: preamble_marker,
        })
    }

    pub fn bind(self, addr: SocketAddr) -> Result<BoundServer<F, T, A>, ServerError> {
        let std_listener = StdTcpListener::bind(addr).map_err(ServerError::Bind)?;
        self.bind_existing_listener(std_listener)
    }

    pub fn bind_existing_listener(
        self,
        std_listener: StdTcpListener,
    ) -> Result<BoundServer<F, T, A>, ServerError> {
        self.bind_to_listener(std_listener)
    }
}
```

This keeps all behavior and type-level guarantees but reduces each impl header from:

```rust
impl<F, T, S, Ser, Ctx, E, Codec> WireframeServer<F, T, S, Ser, Ctx, E, Codec> where ...
```

to:

```rust
impl<F, T, S, A> WireframeServer<F, T, S, A> where ...
```

and eliminates the repeated `(Ser, Ctx, E, Codec)` quartets in this module.
</issue_to_address>

### Comment 4
<location> `src/server/runtime.rs:379` </location>
<code_context>
 /// }
 /// ```
-pub(super) async fn accept_loop<F, T, L>(
+pub(super) async fn accept_loop<F, T, L, Ser, Ctx, E, Codec>(
     listener: Arc<L>,
     factory: F,
</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing the repeated `Ser, Ctx, E, Codec` generics in the runtime functions with a single `App` type parameter (optionally via a small trait) to simplify the signatures and keep the same flexibility.

You can keep the new flexibility without duplicating all the generics and bounds in the accept loop by introducing a single `App` generic and tightening its bounds, instead of threading `Ser, Ctx, E, Codec` everywhere.

Right now, `accept_loop`/`accept_iteration` are carrying:

```rust
pub(super) async fn accept_loop<F, T, L, Ser, Ctx, E, Codec>(...)
where
    F: Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static,
    T: Preamble,
    L: AcceptListener + Send + Sync + 'static,
    Ser: Serializer + FrameMetadata<Frame = Envelope> + Send + Sync + 'static,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{ ... }

async fn accept_iteration<F, T, L, Ser, Ctx, E, Codec>(...)
where
    F: Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static,
    T: Preamble,
    L: AcceptListener + Send + Sync + 'static,
    Ser: Serializer + FrameMetadata<Frame = Envelope> + Send + Sync + 'static,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{ ... }
```

But inside these functions you only really use the app factory and the fact that the app’s serializer implements `FrameMetadata<Frame = Envelope>` (per the other review).

You can make the signatures much lighter by:

1. Introducing a single `App` type parameter with the minimum constraints you need.
2. Expressing `F` in terms of this `App`.
3. Letting `WireframeServer`’s own generic parameters ensure that `App = WireframeApp<Ser, Ctx, E, Codec>` where needed, without repeating them in the accept loop.

For example:

```rust
// Narrow type used by the runtime
pub trait RuntimeApp: FrameMetadata<Frame = Envelope> + Send + Sync + 'static {}

// Blanket impl for your concrete app type; keeps all functionality
impl<Ser, Ctx, E, Codec> RuntimeApp for WireframeApp<Ser, Ctx, E, Codec>
where
    Ser: Serializer + FrameMetadata<Frame = Envelope> + Send + Sync + 'static,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{}
```

Then simplify the loop signatures:

```rust
pub(super) async fn accept_loop<F, T, L, App>(
    listener: Arc<L>,
    factory: F,
    options: AcceptLoopOptions<T>,
) where
    F: Fn() -> App + Send + Sync + Clone + 'static,
    T: Preamble,
    L: AcceptListener + Send + Sync + 'static,
    App: RuntimeApp,
{
    // body unchanged
}

async fn accept_iteration<F, T, L, App>(
    listener: &Arc<L>,
    factory: &F,
    handles: &AcceptHandles<'_, T>,
    delay: Duration,
) -> Option<Duration>
where
    F: Fn() -> App + Send + Sync + Clone + 'static,
    T: Preamble,
    L: AcceptListener + Send + Sync + 'static,
    App: RuntimeApp,
{
    // body unchanged
}
```

On the `WireframeServer` side, you can bind `App` to the existing generics once, instead of repeating them in each function:

```rust
impl<F, T, Ser, Ctx, E, Codec> WireframeServer<F, T, Bound, Ser, Ctx, E, Codec>
where
    F: Fn() -> WireframeApp<Ser, Ctx, E, Codec> + Send + Sync + Clone + 'static,
    T: Preamble,
    Ser: Serializer + FrameMetadata<Frame = Envelope> + Send + Sync + 'static,
    Ctx: Send + 'static,
    E: Packet,
    Codec: FrameCodec,
{
    type App = WireframeApp<Ser, Ctx, E, Codec>;

    pub async fn run(self) {
        // when calling into the runtime, you now rely on App: RuntimeApp
        accept_loop::<_, _, _, Self::App>(/* ... */).await;
    }
}
```

This keeps all the functionality (you still support arbitrary `Ser, Ctx, E, Codec` on `WireframeApp`), but it:

* Removes the need to repeat the generic bounds in `accept_loop` and `accept_iteration`.
* Simplifies tests and examples back to:

```rust
accept_loop(
    listener,
    || WireframeApp::default(),
    AcceptLoopOptions::<()> { /* ... */ },
).await;
```

If you don’t want to introduce a trait, you can still get a big win by going halfway and introducing a simple `App` type parameter with the minimal bound:

```rust
pub(super) async fn accept_loop<F, T, L, App>(...)
where
    F: Fn() -> App + Send + Sync + Clone + 'static,
    T: Preamble,
    L: AcceptListener + Send + Sync + 'static,
    App: FrameMetadata<Frame = Envelope> + Send + Sync + 'static,
{ ... }
```

and rely on the `WireframeServer` impl to ensure that `App = WireframeApp<Ser, Ctx, E, Codec>` satisfies the extra `Serializer + Packet + FrameCodec` constraints elsewhere, rather than restating them in the accept loop.
</issue_to_address>

### Comment 5
<location> `src/codec.rs:19` </location>
<code_context>
+use std::io;
+use tokio_util::codec::{Decoder, Encoder};
+
+/// Trait for pluggable frame codecs supporting different wire protocols.
+pub trait FrameCodec: Send + Sync + 'static {
+    /// Frame type produced by decoding.
</code_context>

<issue_to_address>
**issue (complexity):** Consider tightening the FrameCodec API (using associated types, Bytes frames, and simpler size checks) to reduce indirection and make the codec layer easier to compose and maintain.

You can simplify this substantially without losing any functionality by tightening the trait surface and reusing `LengthDelimitedCodec` directly.

### 1. Use associated types for encoder/decoder

Returning `impl Decoder/Encoder` from `FrameCodec` forces each implementation to have anonymous, non‑nameable types and makes it harder to store heterogeneous codecs or build combinators. Associated types keep the graph explicit and remove the need for ad‑hoc wrapper types elsewhere.

```rust
pub trait FrameCodec: Send + Sync + 'static {
    type Frame: Send + Sync + 'static;
    type Decoder: Decoder<Item = Self::Frame, Error = io::Error> + Send;
    type Encoder: Encoder<Self::Frame, Error = io::Error> + Send;

    fn decoder(&self) -> Self::Decoder;
    fn encoder(&self) -> Self::Encoder;

    fn frame_payload(frame: &Self::Frame) -> &[u8];
    fn wrap_payload(payload: Vec<u8>) -> Self::Frame;

    fn correlation_id(_frame: &Self::Frame) -> Option<u64> { None }

    fn max_frame_length(&self) -> usize;
}
```

This preserves your current functionality but reduces the “dynamic” feel of the API and makes composition easier.

### 2. Drop `LengthDelimitedAdapter` by using `Bytes` as the frame

`LengthDelimitedAdapter` only translates `Bytes``Vec<u8>` and adds an extra layer around `LengthDelimitedCodec`. If your frames are just payloads, you can keep everything zero‑copy by making `Frame = Bytes` and using `LengthDelimitedCodec` directly:

```rust
#[derive(Clone, Debug)]
pub struct LengthDelimitedFrameCodec {
    max_frame_length: usize,
}

impl LengthDelimitedFrameCodec {
    #[must_use]
    pub fn new(max_frame_length: usize) -> Self {
        Self {
            max_frame_length: clamp_frame_length(max_frame_length),
        }
    }

    fn new_inner_codec(&self) -> LengthDelimitedCodec {
        LengthDelimitedCodec::builder()
            .max_frame_length(self.max_frame_length)
            .new_codec()
    }
}

impl FrameCodec for LengthDelimitedFrameCodec {
    type Frame = Bytes;
    type Decoder = LengthDelimitedCodec;
    type Encoder = LengthDelimitedCodec;

    fn decoder(&self) -> Self::Decoder {
        self.new_inner_codec()
    }

    fn encoder(&self) -> Self::Encoder {
        self.new_inner_codec()
    }

    fn frame_payload(frame: &Self::Frame) -> &[u8] {
        frame.as_ref()
    }

    fn wrap_payload(payload: Vec<u8>) -> Self::Frame {
        Bytes::from(payload)
    }

    fn max_frame_length(&self) -> usize {
        self.max_frame_length
    }
}
```

Callers that need `Vec<u8>` can still do `frame.to_vec()`, but the core codec remains zero‑copy and doesn’t need a bespoke adapter.

### 3. Consider simplifying the “reject oversized payloads” policy

Right now the test `length_delimited_codec_rejects_oversized_payloads` forces the encoder to perform its own length check and fail with `InvalidInput` for oversized frames. That means:

- You can’t rely solely on `LengthDelimitedCodec`’s `max_frame_length`, since it only enforces this on decode.
- You’ll need to track `max_frame_length` again in your encoder logic, which re‑introduces duplication.

If you don’t need strict encode‑time validation, you can simplify by treating `max_frame_length` as a decode‑time bound only and dropping that test (and any encode‑side clamping). If you do need the check, keep the associated‑type simplification and still avoid the adapter by checking length before calling `encode`:

```rust
impl FrameCodec for LengthDelimitedFrameCodec {
    // as above...

    fn encoder(&self) -> impl Encoder<Self::Frame, Error = io::Error> + Send {
        let max = self.max_frame_length;
        // small local wrapper instead of a separate top‑level adapter type
        struct CheckedEncoder {
            inner: LengthDelimitedCodec,
            max: usize,
        }

        impl Encoder<Bytes> for CheckedEncoder {
            type Error = io::Error;

            fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
                if item.len() > self.max {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidInput,
                        "frame exceeds max_frame_length",
                    ));
                }
                self.inner.encode(item, dst)
            }
        }

        CheckedEncoder { inner: self.new_inner_codec(), max }
    }
}
```

This keeps your current semantics (encoded frames are validated) but avoids a standalone `LengthDelimitedAdapter` type and still reuses `LengthDelimitedCodec` as the core implementation.
</issue_to_address>

### Comment 6
<location> `src/codec.rs:23` </location>
<code_context>
+use tokio_util::codec::{Decoder, Encoder};
+
+/// Trait for pluggable frame codecs supporting different wire protocols.
+pub trait FrameCodec: Send + Sync + 'static {
+    /// Frame type produced by decoding.
+    type Frame: Send + Sync + 'static;
</code_context>

<issue_to_address>
**issue (review_instructions):** Add both unit and behavioural tests that exercise the new FrameCodec abstraction and its default LengthDelimitedFrameCodec in realistic I/O paths.

Cover the new codec layer with more than the in-module unit tests. The current tests only validate basic round-tripping and clamping on the adapter; they do not exercise:

- Using `FrameCodec` with a `Framed` stream end-to-end (decode, route, encode).
- Failure paths (e.g. malformed headers, EOF mid-frame) and how they surface.
- Integration with `WireframeApp` and `WireframeServer` when using non-default codecs.

Add behavioural tests under `tests/` (for example, in `tests/frame_codec.rs`) that stand up a minimal app/server using `LengthDelimitedFrameCodec` and assert that requests and responses flow correctly via the new trait, and complement them with focused unit tests for error cases on the adapters.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 7
<location> `src/app/connection.rs:90` </location>
<code_context>
 }

-impl<S, C, E> WireframeApp<S, C, E>
+impl<S, C, E, F> WireframeApp<S, C, E, F>
 where
     S: Serializer + Send + Sync,
</code_context>

<issue_to_address>
**issue (review_instructions):** Cover the new codec-parameterised connection path with behavioural tests to ensure framing, timeouts, and fragmentation still work.

You introduce a codec-parameterised `WireframeApp` and change the connection handling to use `FrameCodec` (e.g. `CombinedCodec`, `F::wrap_payload`, `F::frame_payload`, and `self.codec.max_frame_length()`), but there are no new tests exercising these changes.

Add behavioural tests that:
- Start a `WireframeServer` / accept loop with the default `LengthDelimitedFrameCodec` and verify end-to-end request/response behaviour still matches the previous semantics (including read timeouts and max-frame enforcement).
- Use a custom codec implementation to confirm that frames are correctly decoded, routed, and encoded.

Also add unit-level tests around `fragmentation_config`, `decode_envelope`, and the new clamping behaviour in `handle_connection` to ensure envelope parsing and buffer sizing remain correct with arbitrary codecs.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 8
<location> `src/app/frame_handling.rs:51` </location>
<code_context>
 }

-pub(crate) struct ResponseContext<'a, S, W>
+pub(crate) struct ResponseContext<'a, S, W, F, C>
 where
     S: Serializer + Send + Sync,
</code_context>

<issue_to_address>
**issue (review_instructions):** Test the updated response forwarding logic with codec-aware framing, including fragmented responses and error paths.

`ResponseContext` and `forward_response` now depend on a generic `FrameCodec` via `send_response_payload::<F, W, C>`, which wraps payloads into frames instead of sending raw `Vec<u8>` to a `LengthDelimitedCodec`. This is a behavioural change in how responses are framed.

Add tests that:
- Verify `forward_response` correctly serializes and sends responses using the default length-delimited codec, including fragmented `Response::Stream` cases.
- Confirm that error logging and metrics (`inc_handler_errors`) still fire as expected when `send_response_payload` fails.
- Exercise the new path with at least one non-default codec to ensure `F::wrap_payload` is applied consistently for handler responses.

These can be a mix of unit tests (mocked `HandlerService` and in-memory framed stream) and higher-level behavioural tests using a minimal app.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 9
<location> `src/app/connection.rs:376` </location>
<code_context>
     /// Increment deserialization failures and close the connection if the threshold is exceeded.
     fn handle_decode_failure(
         deser_failures: &mut u32,
+        correlation_id: Option<u64>,
</code_context>

<issue_to_address>
**issue (review_instructions):** Add a regression test for the updated handle_decode_failure logging and threshold behaviour.

`handle_decode_failure` is changed to accept an explicit `correlation_id` and log it, and it still enforces the `MAX_DESER_FAILURES` threshold. This is effectively a bug fix / observability fix but there is no test covering it.

Add a focused unit test that:
- Calls `decode_envelope` with frames that fail deserialization and asserts that `handle_decode_failure` increments the counter and returns the expected `io::Error` once the threshold is exceeded.
- Verifies that the correlation ID is plumbed through when available (you can assert on the log record via a test logger or, at minimum, on the value passed into `handle_decode_failure`).

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
Bug fixes *must* be demonstrated by a test.

</details>
</issue_to_address>

### Comment 10
<location> `examples/hotline_codec.rs:111` </location>
<code_context>
+    }
+}
+
+impl FrameCodec for HotlineFrameCodec {
+    type Frame = HotlineFrame;
+
</code_context>

<issue_to_address>
**issue (review_instructions):** Back the new example codecs with unit tests that validate their framing logic independently of the example binaries.

The Hotline and MySQL codecs add new framing behaviour (custom headers, length checks, correlation IDs) but are only exercised via examples. Examples compiling is not sufficient coverage for these new features.

Extract the core adapter logic into testable units (or import the example types in a `tests/` module) and add unit tests that:
- Validate correct encoding/decoding of headers and payloads, including boundary sizes.
- Assert that oversized or malformed frames produce the expected `io::Error` variants.
- Confirm that `correlation_id` is derived correctly from the protocol-specific header fields.

Complement these with at least one behavioural test that wires a `WireframeApp` with a custom codec and sends a request through a `Framed` stream using the example codec, to satisfy the behavioural-test requirement.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 11
<location> `docs/adr-004-pluggable-protocol-codecs.md:21` </location>
<code_context>
+Hotline, MySQL, and Redis RESP) from reusing Wireframe's routing, middleware,
+and serialisation infrastructure.
+
+We need a pluggable framing layer that can:
+
+- Decode frames with protocol-specific headers.
</code_context>

<issue_to_address>
**issue (review_instructions):** The sentence uses first-person plural ("We"), which the style instructions prohibit.

Consider rephrasing in an impersonal form, for example: "A pluggable framing layer is required that can:" or "The system requires a pluggable framing layer that can:"

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Avoid 2nd person or 1st person pronouns ("I", "you", "we").

</details>
</issue_to_address>

### Comment 12
<location> `docs/adr-004-pluggable-protocol-codecs.md:19` </location>
<code_context>
+`src/app/connection.rs` and `src/app/frame_handling.rs`). This makes framing
+inflexible and prevents protocols with alternative framing rules (such as
+Hotline, MySQL, and Redis RESP) from reusing Wireframe's routing, middleware,
+and serialisation infrastructure.
+
+We need a pluggable framing layer that can:
</code_context>

<issue_to_address>
**issue (review_instructions):** "serialisation" uses -isation spelling instead of the requested -ization/-izer style for en-GB-oxendic.

To match the specified en-GB-oxendic spelling convention, please change "serialisation" to "serialization" here.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Use en-GB-oxendic (-ize / -yse / -our) spelling and grammar.

</details>
</issue_to_address>

### Comment 13
<location> `docs/execplans/pluggable-protocol-codecs.md:13` </location>
<code_context>
+
+## Purpose / Big Picture
+
+Wireframe must support multiple wire protocol framing schemes (Hotline, MySQL,
+and Redis RESP) without rewriting routing, middleware, or serialisation. A new
+pluggable codec layer allows protocol-specific framing while preserving default
</code_context>

<issue_to_address>
**issue (review_instructions):** "RESP" (Redis Serialization Protocol) is introduced later in this sentence without being expanded on first use.

To satisfy the acronym rule, consider expanding RESP on first use, for example: "Redis Serialization Protocol (RESP)".

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 14
<location> `docs/execplans/pluggable-protocol-codecs.md:14` </location>
<code_context>
+## Purpose / Big Picture
+
+Wireframe must support multiple wire protocol framing schemes (Hotline, MySQL,
+and Redis RESP) without rewriting routing, middleware, or serialisation. A new
+pluggable codec layer allows protocol-specific framing while preserving default
+length-delimited behaviour for existing users. Success is visible when a custom
</code_context>

<issue_to_address>
**issue (review_instructions):** "serialisation" uses -isation spelling instead of the requested -ization/-izer style for en-GB-oxendic.

Please update "serialisation" to "serialization" here to align with the specified en-GB-oxendic (-ize/-ization) convention.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Use en-GB-oxendic (-ize / -yse / -our) spelling and grammar.

</details>
</issue_to_address>

### Comment 15
<location> `docs/adr-005-serializer-abstraction.md:1` </location>
<code_context>
+# Architectural decision record (ADR) 005: serialiser abstraction beyond bincode
+
+## Status
</code_context>

<issue_to_address>
**issue (review_instructions):** "serialiser" in the heading uses -iser instead of the requested -izer form for en-GB-oxendic.

To follow the en-GB-oxendic guidance, this heading should use "serializer" rather than "serialiser".

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Use en-GB-oxendic (-ize / -yse / -our) spelling and grammar.

</details>
</issue_to_address>

### Comment 16
<location> `docs/adr-005-serializer-abstraction.md:23` </location>
<code_context>
+targets derivable encoders/decoders, and message versioning is expected to be
+an evolution path.[^router-design][^message-versioning]
+
+We need to decouple message encoding/decoding from bincode while preserving
+backward compatibility and performance.
+
</code_context>

<issue_to_address>
**issue (review_instructions):** This sentence uses first-person plural ("We"), which is disallowed by the style rules.

Consider rephrasing in an impersonal form, for example: "Message encoding/decoding must be decoupled from bincode while preserving…"

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Avoid 2nd person or 1st person pronouns ("I", "you", "we").

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/app/connection.rs Outdated
Comment thread src/app/builder.rs
Comment thread src/server/config/binding.rs
Comment thread src/server/runtime.rs Outdated
Comment thread src/codec.rs
Comment thread docs/adr-004-pluggable-protocol-codecs.md Outdated
Comment thread docs/execplans/pluggable-protocol-codecs.md
Comment thread docs/execplans/pluggable-protocol-codecs.md Outdated
Comment thread docs/adr-005-serializer-abstraction.md Outdated
Comment thread docs/adr-005-serializer-abstraction.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/server/runtime.rs (1)

1-675: File exceeds 400-line guideline.

At 675 lines, this file exceeds the 400-line limit specified in the coding guidelines. Decompose the module by extracting:

  • BackoffConfig and its impl into a separate backoff.rs submodule.
  • Test helpers (setup_backoff_mock_listener, assert_backoff_intervals) and tests into a dedicated tests.rs submodule.

As per coding guidelines: "Files must not exceed 400 lines in length... Large modules must be decomposed."

src/app/connection.rs (1)

1-419: File slightly exceeds 400-line guideline.

At 419 lines, this file marginally exceeds the 400-line limit. Extract CombinedCodec and its implementations into a separate combined_codec.rs submodule to improve modularity.

As per coding guidelines: "Files must not exceed 400 lines in length."

src/app/builder.rs (1)

1-448: File exceeds 400-line guideline.

At 448 lines, this file exceeds the 400-line limit. Extract the following into separate submodules:

  • Connection setup/teardown types and impls into lifecycle.rs.
  • The Handler and Middleware types into middleware_types.rs (or relocate to the middleware module).

As per coding guidelines: "Files must not exceed 400 lines in length... Large modules must be decomposed."

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d83eee and c3e0548.

📒 Files selected for processing (26)
  • .markdownlint-cli2.jsonc
  • Cargo.toml
  • Makefile
  • docs/adr-001-multi-packet-streaming-response-api.md
  • docs/adr-002-streaming-requests-and-shared-message-assembly.md
  • docs/adr-003-replace-cucumber-with-rstest-bdd.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/adr-005-serializer-abstraction.md
  • docs/documentation-style-guide.md
  • docs/execplans/pluggable-protocol-codecs.md
  • docs/roadmap.md
  • docs/users-guide.md
  • examples/hotline_codec.rs
  • examples/mysql_codec.rs
  • src/app/builder.rs
  • src/app/connection.rs
  • src/app/frame_handling.rs
  • src/codec.rs
  • src/lib.rs
  • src/server/config/binding.rs
  • src/server/config/mod.rs
  • src/server/config/preamble.rs
  • src/server/connection.rs
  • src/server/mod.rs
  • src/server/runtime.rs
  • tests/frame_codec.rs
🧰 Additional context used
📓 Path-based instructions (6)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • src/lib.rs
  • src/app/frame_handling.rs
  • src/server/mod.rs
  • examples/mysql_codec.rs
  • tests/frame_codec.rs
  • examples/hotline_codec.rs
  • src/server/config/binding.rs
  • src/server/connection.rs
  • src/server/config/preamble.rs
  • src/server/runtime.rs
  • src/app/connection.rs
  • src/codec.rs
  • src/app/builder.rs
  • src/server/config/mod.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • src/lib.rs
  • src/app/frame_handling.rs
  • src/server/mod.rs
  • examples/mysql_codec.rs
  • tests/frame_codec.rs
  • examples/hotline_codec.rs
  • src/server/config/binding.rs
  • src/server/connection.rs
  • src/server/config/preamble.rs
  • src/server/runtime.rs
  • src/app/connection.rs
  • src/codec.rs
  • src/app/builder.rs
  • src/server/config/mod.rs
docs/**/*.md

📄 CodeRabbit inference engine (docs/contents.md)

Follow the documentation style guide conventions when writing project documentation

docs/**/*.md: Use British English based on the Oxford English Dictionary (en-GB-oxendict), including: -ize suffix in words like 'realize' and 'organization', -lyse suffix in words like 'analyse', -our suffix in words like 'colour', -re suffix in words like 'centre', double 'l' in words like 'cancelled', maintain 'e' in words like 'likeable', -ogue suffix in words like 'catalogue'
The word 'outwith' is acceptable in British English documentation
Keep US spelling when used in an API (for example, 'color')
Use the Oxford comma in documentation: 'ships, planes, and hovercraft' where it aids comprehension
Treat company names as collective nouns: 'Lille Industries are expanding'
Write headings in sentence case in documentation
Use Markdown headings (#, ##, ###, and so on) in order without skipping levels
Follow markdownlint recommendations for Markdown files
Always provide a language identifier for fenced code blocks in documentation; use 'plaintext' for non-code text
Use '-' as the first level bullet and renumber lists when items change in documentation
Prefer inline links using 'text' or angle brackets around the URL in documentation
Ensure blank lines before and after bulleted lists and fenced blocks in documentation
Ensure tables have a delimiter line below the header row in documentation
Expand any uncommon acronym on first use in documentation (for example, Continuous Integration (CI))
Wrap paragraphs at 80 columns in documentation
Wrap code at 120 columns in documentation
Do not wrap tables in documentation
Use footnotes referenced with '[^label]' in documentation
Include Mermaid diagrams where they add clarity to documentation
When embedding figures in documentation, use 'alt text' and provide brief alt text describing the content
Add a short description before each Mermaid diagram so screen readers can understand it

Write official documentation with se...

Files:

  • docs/roadmap.md
  • docs/users-guide.md
  • docs/adr-005-serializer-abstraction.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/documentation-style-guide.md
  • docs/execplans/pluggable-protocol-codecs.md
docs/**/*.{rs,md}

📄 CodeRabbit inference engine (docs/rust-doctest-dry-guide.md)

Every doctest should validate the public API of a crate from the perspective of an external user, treating each documentation test as a separate temporary crate that imports the library as an external dependency

Files:

  • docs/roadmap.md
  • docs/users-guide.md
  • docs/adr-005-serializer-abstraction.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/documentation-style-guide.md
  • docs/execplans/pluggable-protocol-codecs.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by running make nixie.
Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Markdown tables and headings must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes in Markdown.

Files:

  • docs/roadmap.md
  • docs/users-guide.md
  • docs/adr-005-serializer-abstraction.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/documentation-style-guide.md
  • docs/execplans/pluggable-protocol-codecs.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/roadmap.md
  • docs/users-guide.md
  • docs/adr-005-serializer-abstraction.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/documentation-style-guide.md
  • docs/execplans/pluggable-protocol-codecs.md
docs/**

📄 CodeRabbit inference engine (docs/roadmap.md)

Provide comprehensive documentation including user guide, tutorials, and troubleshooting guidance

Files:

  • docs/roadmap.md
  • docs/users-guide.md
  • docs/adr-005-serializer-abstraction.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/documentation-style-guide.md
  • docs/execplans/pluggable-protocol-codecs.md
Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up-to-date.
All crate versions specified in Cargo.toml must use SemVer-compatible caret requirements (e.g., some-crate = "1.2.3"). Wildcard (*) or open-ended inequality (>=) version requirements are strictly forbidden. Tilde requirements (~) should only be used where a dependency must be locked to patch-level updates for a specific, documented reason.

Files:

  • Cargo.toml
🧬 Code graph analysis (12)
src/app/frame_handling.rs (1)
src/codec.rs (2)
  • wrap_payload (37-37)
  • wrap_payload (131-131)
src/server/mod.rs (2)
src/app/builder.rs (1)
  • serializer (404-413)
src/app/connection.rs (1)
  • serializer (208-208)
examples/mysql_codec.rs (1)
examples/hotline_codec.rs (11)
  • new (29-29)
  • new (38-38)
  • max_frame_length (133-133)
  • decode (45-79)
  • encode (85-108)
  • decoder (114-116)
  • encoder (118-120)
  • frame_payload (122-122)
  • wrap_payload (124-129)
  • correlation_id (131-131)
  • main (136-143)
tests/frame_codec.rs (1)
src/codec.rs (16)
  • new (60-64)
  • new (84-90)
  • max_frame_length (45-45)
  • max_frame_length (68-68)
  • max_frame_length (133-133)
  • decode (97-101)
  • encode (113-115)
  • decoder (28-28)
  • decoder (121-123)
  • encoder (31-31)
  • encoder (125-127)
  • frame_payload (34-34)
  • frame_payload (129-129)
  • wrap_payload (37-37)
  • wrap_payload (131-131)
  • correlation_id (42-42)
src/server/config/binding.rs (6)
src/app/builder.rs (1)
  • serializer (404-413)
src/app/connection.rs (1)
  • serializer (208-208)
tests/middleware_order.rs (1)
  • serializer (94-94)
tests/fragment_transport.rs (1)
  • serializer (135-135)
tests/preamble.rs (7)
  • WireframeServer (61-63)
  • WireframeServer (247-248)
  • WireframeServer (283-284)
  • WireframeServer (334-335)
  • WireframeServer (370-371)
  • WireframeServer (431-432)
  • WireframeServer (527-531)
src/server/config/tests.rs (2)
  • WireframeServer (73-73)
  • WireframeServer (204-206)
src/server/connection.rs (4)
src/preamble.rs (1)
  • read_preamble (112-138)
src/app/builder.rs (2)
  • serializer (404-413)
  • default (125-142)
src/app/connection.rs (2)
  • serializer (208-208)
  • process_stream (272-320)
src/server/test_util.rs (1)
  • factory (18-20)
src/server/config/preamble.rs (3)
src/app/builder.rs (1)
  • serializer (404-413)
src/app/connection.rs (1)
  • serializer (208-208)
src/server/test_util.rs (1)
  • WireframeServer (89-89)
src/server/runtime.rs (5)
src/app/builder.rs (1)
  • serializer (404-413)
src/app/connection.rs (1)
  • serializer (208-208)
tests/middleware_order.rs (1)
  • serializer (94-94)
tests/fragment_transport.rs (1)
  • serializer (135-135)
tests/common/mod.rs (1)
  • factory (28-31)
src/app/connection.rs (1)
src/codec.rs (14)
  • decoder (28-28)
  • decoder (121-123)
  • encoder (31-31)
  • encoder (125-127)
  • decode (97-101)
  • encode (113-115)
  • wrap_payload (37-37)
  • wrap_payload (131-131)
  • max_frame_length (45-45)
  • max_frame_length (68-68)
  • max_frame_length (133-133)
  • correlation_id (42-42)
  • frame_payload (34-34)
  • frame_payload (129-129)
src/codec.rs (4)
src/frame/format.rs (1)
  • bytes (54-54)
src/request.rs (1)
  • value (192-197)
tests/frame_codec.rs (10)
  • decoder (100-102)
  • encoder (104-106)
  • frame_payload (108-108)
  • wrap_payload (110-110)
  • correlation_id (112-112)
  • max_frame_length (114-114)
  • new (27-27)
  • new (36-36)
  • decode (43-67)
  • encode (73-94)
src/app/connection.rs (3)
  • new (45-45)
  • decode (55-57)
  • encode (70-72)
src/app/builder.rs (2)
src/codec.rs (6)
  • clamp_frame_length (15-17)
  • new (60-64)
  • new (84-90)
  • max_frame_length (45-45)
  • max_frame_length (68-68)
  • max_frame_length (133-133)
src/fragment/config.rs (1)
  • for_frame_budget (35-53)
src/server/config/mod.rs (3)
src/app/builder.rs (1)
  • serializer (404-413)
src/app/connection.rs (1)
  • serializer (208-208)
src/server/config/tests.rs (2)
  • WireframeServer (73-73)
  • WireframeServer (204-206)
🪛 LanguageTool
docs/roadmap.md

[uncategorized] ~396-~396: Do not mix variants of the same word (‘adapter’ and ‘adaptor’) within a single text.
Context: ...ate LengthDelimitedFrameCodec and any adapters to use the new payload type. - [ ...

(EN_WORD_COHERENCY)


[uncategorized] ~398-~398: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: .... - [ ] Reuse a per-connection encoder so sequence counters can advance deter...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~406-~406: Possible missing comma found.
Context: ...ame is surfaced to handlers or protocol hooks and add tests for partial-frame clo...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~413-~413: Do not mix variants of the same word (‘adapter’ and ‘adaptor’) within a single text.
Context: ...ation. - [ ] Update the default codec adapter to avoid Bytes to Vec<u8> copying ...

(EN_WORD_COHERENCY)


[grammar] ~416-~416: Did you mean the noun “reception” (= the act of receiving) or “receipt” (= invoice)?
Context: ...hmark to confirm payloads reuse the receive buffer where possible. ### 7.6.2. Frag...

(PREPOSITION_VERB)


[uncategorized] ~418-~418: Do not mix variants of the same word (‘adapter’ and ‘adaptor’) within a single text.
Context: ...er where possible. ### 7.6.2. Fragment adapter alignment - [ ] 7.6.2.1. Introduce a `...

(EN_WORD_COHERENCY)


[uncategorized] ~426-~426: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...ilder. - [ ] Expose a public purge API so callers can drive timeout eviction. -...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~428-~428: Do not mix variants of the same word (‘serialisation’ and ‘serialization’) within a single text.
Context: ...order for codec, fragmentation, and serialisation layers. - [ ] Define and implement du...

(EN_WORD_COHERENCY)


[uncategorized] ~460-~460: Do not mix variants of the same word (‘adapter’ and ‘adaptor’) within a single text.
Context: ... a serializer-agnostic message trait or adapter layer for Message types. - [ ] ...

(EN_WORD_COHERENCY)


[style] ~463-~463: Would you like to use the Oxford spelling “deserialization”? The spelling ‘deserialisation’ is also correct.
Context: ...ne how frame metadata is exposed to the deserialisation context to enable version negotiati...

(OXFORD_SPELLING_Z_NOT_S)

docs/adr-005-serializer-abstraction.md

[style] ~17-~17: Would you like to use the Oxford spelling “serialization”? The spelling ‘serialisation’ is also correct.
Context: ...fork the library. This leaks a concrete serialisation choice into otherwise protocol-agnostic...

(OXFORD_SPELLING_Z_NOT_S)


[grammar] ~21-~21: Please add a punctuation mark at the end of paragraph.
Context: ...g is expected to be an evolution path.[^router-design][^message-versioning] We need to decou...

(PUNCTUATION_PARAGRAPH_END)


[style] ~28-~28: Would you like to use the Oxford spelling “serialization”? The spelling ‘serialisation’ is also correct.
Context: ...Decision Drivers - Support alternative serialisation formats without forking. - Preserve exi...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~30-~30: Would you like to use the Oxford spelling “deserialization”? The spelling ‘deserialisation’ is also correct.
Context: ...ed integrations where possible. - Allow deserialisation to access protocol metadata for version...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~71-~71: Would you like to use the Oxford spelling “deserializing”? The spelling ‘deserialising’ is also correct.
Context: ... | Medium | | Metadata-aware deserialising | Poor | Strong ...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~75-~75: Would you like to use the Oxford spelling “serialization”? The spelling ‘serialisation’ is also correct.
Context: ... | Table 1: Trade-offs between serialisation abstraction options. ## Decision Outc...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~82-~82: Would you like to use the Oxford spelling “deserialization”? The spelling ‘deserialisation’ is also correct.
Context: ...allows frame metadata to participate in deserialisation, supports version negotiation, and avoi...

(OXFORD_SPELLING_Z_NOT_S)


[uncategorized] ~83-~83: Possible missing article found.
Context: ...sion negotiation, and avoids committing Wireframe to a single serialiser. ## Goals and N...

(AI_HYDRA_LEO_MISSING_THE)


[style] ~132-~132: Would you like to use the Oxford spelling “serialization”? The spelling ‘serialisation’ is also correct.
Context: ...Architectural Rationale Decoupling the serialisation boundary aligns with Wireframe’s goal o...

(OXFORD_SPELLING_Z_NOT_S)

docs/adr-004-pluggable-protocol-codecs.md

[typographical] ~5-~5: To join two clauses or introduce examples, consider using an em dash.
Context: ...ble protocol codecs ## Status Accepted - 2025-12-30: introduce a FrameCodec tra...

(DASH_RULE)


[style] ~19-~19: Would you like to use the Oxford spelling “serialization”? The spelling ‘serialisation’ is also correct.
Context: ...ng Wireframe's routing, middleware, and serialisation infrastructure. We need a pluggable fr...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~41-~41: Since ownership is already implied, this phrasing may be redundant.
Context: ...llow multiple wire protocols to plug in their own framing rules. - Preserve protocol meta...

(PRP_OWN)


[grammar] ~79-~79: The usual collocation for “Full” is “of”, not “with”. Did you mean “Full of”?
Context: ...compatibility | Full | Full with defaults | Medium (API changes)...

(FULL_WITH_OF)


[uncategorized] ~86-~86: Possible missing comma found.
Context: ...trait with a default length-delimited implementation and makeWireframeApp` generic over a ...

(AI_HYDRA_LEO_MISSING_COMMA)


[style] ~145-~145: Would you like to use the Oxford spelling “serialization”? The spelling ‘serialisation’ is also correct.
Context: ...ction. - Replacing the existing message serialisation format. - Introducing asynchronous code...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~146-~146: Would you like to use the Oxford spelling “initialization”? The spelling ‘initialisation’ is also correct.
Context: ...ormat. - Introducing asynchronous codec initialisation. ## Migration Plan ### Phase 1: Trait...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~156-~156: Would you like to use the Oxford spelling “Parameterize”? The spelling ‘Parameterise’ is also correct.
Context: ... default codec behaviour. ### Phase 2: Parameterise WireframeApp (breaking change) - Add...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~160-~160: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nd update the default implementation. - Add .with_codec() for builder ergonomics....

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~164-~164: Would you like to use the Oxford spelling “Parameterize”? The spelling ‘Parameterise’ is also correct.
Context: ... Phase 3: Update connection handling - Parameterise FrameHandlingContext and `ResponseCon...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~199-~199: Since ownership is already implied, this phrasing may be redundant.
Context: ... while the Connection actor retains its own framing logic. This makes protocol hook...

(PRP_OWN)

docs/documentation-style-guide.md

[style] ~232-~232: Would you like to use the Oxford spelling “summarize”? The spelling ‘summarise’ is also correct.
Context: ...tion:** State the chosen approach and summarise the rationale. For Proposed ADRs, des...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~249-~249: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...sections (e.g. phases, option names). - Use tables to compare options when multiple...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~252-~252: The singular proper name ‘Rust’ must be used with a third-person or a past tense verb.
Context: ...hes. Use no_run for illustrative Rust code that should not be executed. - Add sc...

(HE_VERB_AGR)

docs/execplans/pluggable-protocol-codecs.md

[style] ~14-~14: Would you like to use the Oxford spelling “serialization”? The spelling ‘serialisation’ is also correct.
Context: ...thout rewriting routing, middleware, or serialisation. A new pluggable codec layer allows pro...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~118-~118: Would you like to use the Oxford spelling “initialize”? The spelling ‘initialise’ is also correct.
Context: .... - Add codec: F to the struct and initialise it in Default. - Add builder metho...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~125-~125: Would you like to use the Oxford spelling “Parameterize”? The spelling ‘Parameterise’ is also correct.
Context: ...with FrameCodec decoder/encoder. - Parameterise FrameHandlingContext and `ResponseCon...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~127-~127: Would you like to use the Oxford spelling “deserialization”? The spelling ‘deserialisation’ is also correct.
Context: ...c::frame_payload()to access bytes for deserialisation. - UseFrameCodec::wrap_payload()` ...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~189-~189: Consider using a different verb for a more formal wording.
Context: ...ly the changes introduced by that step, correct the issue, and reapply the edit. Use th...

(FIX_RESOLVE)


[uncategorized] ~221-~221: Possible missing comma found.
Context: ..._response_framed_with_codecfor custom codecs and keep send_response_framed` for t...

(AI_HYDRA_LEO_MISSING_COMMA)

🔍 Remote MCP

I'll search for relevant context to help review this PR effectively. Let me gather information about the FrameCodec architecture, the refactoring suggestions, and the architectural decisions.The search didn't identify the specific repository. Let me try a more targeted search based on the project structure and names visible in the PR context.Based on the PR context provided, I can see the project details but need to work with what I have. Let me examine the specific information requested about the FrameCodec trait and builder pattern more directly by analyzing the provided summaries. Since I don't have the specific GitHub repository identified, let me focus on the technical content review using the detailed context provided.

Summary of Key Findings for PR Review

Based on the comprehensive context provided, here are the critical findings relevant to reviewing this pluggable protocol codecs PR:

1. Core Architecture: FrameCodec Trait Design

The PR introduces a pluggable framing abstraction through a FrameCodec trait with the following key characteristics:

  • Associated Frame Type: Each codec implementation defines its own Frame type (e.g., Vec<u8> for length-delimited, custom types for Hotline/MySQL)

  • Required Methods:

    • decoder() and encoder() for frame codec creation
    • frame_payload() to extract payload from frames
    • wrap_payload() to create frames from payloads
    • max_frame_length() to report frame size limits
    • Optional correlation_id() for extracting trace IDs from frames
  • Default Implementation: LengthDelimitedFrameCodec provides backward compatibility with clamped max frame length enforcement

2. Generic Propagation Through the Stack

The PR successfully parameterizes both WireframeApp and WireframeServer with a new F: FrameCodec generic parameter:

WireframeApp signature changes:

  • From: WireframeApp<S, C, E>
  • To: WireframeApp<S, C, E, F> with F: FrameCodec = LengthDelimitedFrameCodec default

Impact on Public API:

  • protocol field type updated from Arc<dyn WireframeProtocol<Frame = Vec<u8>>> to Arc<dyn WireframeProtocol<Frame = F::Frame>>
  • fragmentation config now derives from codec.max_frame_length() instead of hard-coded buffer_capacity field
  • New builder method: with_codec<F2>() to swap codecs while preserving other config

WireframeServer signature changes:

  • Expanded from WireframeServer<F, T = (), S = Unbound> to include Ser, Ctx, E, Codec generics with defaults
  • Factory trait updated: F: Fn() -> WireframeApp<Ser, Ctx, E, Codec> + ...

3. Code Duplication Refactoring

The PR addresses a reported code duplication issue (mentioned in comments) between serializer() and with_codec() methods in src/app/builder.rs:

  • Solution implemented: A private helper method rebuild_with_params() centralizes the reconstruction logic
  • Helper signature:
    fn rebuild_with_params<S2, F2>(
      self, 
      serializer: S2, 
      codec: F2, 
      protocol: Option<Arc<dyn WireframeProtocol<Frame = F2::Frame, ProtocolError = ()>>>, 
      fragmentation: Option<FragmentationConfig>
    ) -> WireframeApp<S2, C, E, F2>
    
  • Benefits: Eliminates field-by-field reconstruction duplication; properly handles type parameter changes

4. Enhanced Features

  • New send helper: send_response_framed_with_codec() allows custom codecs to encode frames with proper serialization
  • Correlation ID propagation: Deserialization failures now include correlation IDs extracted from frame metadata when available
  • Integration tests: tests/frame_codec.rs validates custom codec round-tripping with a TaggedFrame example
  • Example implementations: Hotline and MySQL codecs demonstrate protocol-specific framing (4-byte header for MySQL, 20-byte header for Hotline)

5. Documentation and Roadmap Updates

  • ADR-004 documents the pluggable codec architectural decision
  • ADR-005 covers serializer abstraction layering
  • Execution Plan provides concrete implementation steps and validation commands
  • Roadmap Phase 7.6 details codec hardening across fragmentation, benchmarks, and property-based testing

6. Backward Compatibility

  • Length-delimited framing remains the default for all types
  • Existing code using WireframeApp continues to work via default generic parameters
  • buffer_capacity() method specialized for LengthDelimitedFrameCodec preserves the original API surface

Risk Considerations for Review

  • Type Explosion: The expanded generic parameters (Ser, Ctx, E, Codec) increase type complexity in error messages and compilation time
  • Trait Bounds Propagation: Five new trait bounds (Serializer, FrameCodec, Packet) appear across multiple impl blocks—consistency should be verified
  • Frame Type Variance: Protocol implementations must be generic over F::Frame; test coverage for different frame types is critical
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Sourcery review
🔇 Additional comments (63)
.markdownlint-cli2.jsonc (1)

13-21: LGTM: Improved ignore list formatting and new exclusions.

The multi-line formatting enhances maintainability, and the new exclusions (Terraform state, UV cache, and CRUSH.md) are appropriate for generated or transient content.

Cargo.toml (2)

139-173: LGTM: New examples properly feature-gated.

The five new example entries (including the codec examples hotline_codec and mysql_codec) are correctly structured and feature-gated behind examples, aligning with the pluggable codec architecture.


176-184: LGTM: Test configuration properly structured.

The named cucumber test and new concurrency_loom test are correctly configured with appropriate harness and feature-gate settings.

src/lib.rs (2)

15-15: LGTM: Public re-exports enhance ergonomics.

The re-exports of FrameCodec and LengthDelimitedFrameCodec provide a clean public API surface for the pluggable codec abstraction.


9-9: Approve codec module addition.

The new codec module is appropriately positioned in the module hierarchy and includes proper module-level documentation explaining its purpose and utility.

Makefile (2)

8-8: LGTM: Markdown linter tooling updated.

The switch from markdownlint to markdownlint-cli2 aligns with the updated configuration file (.markdownlint-cli2.jsonc) and uses an appropriate glob pattern.

Also applies to: 44-44


47-47: LGTM: Nixie sandbox flag added.

The --no-sandbox flag is appropriate for environments where sandboxing may not be available or necessary.

docs/adr-005-serializer-abstraction.md (1)

1-140: LGTM: ADR structure and content are comprehensive.

The ADR follows the documented style guide structure with all required sections (Status, Date, Context, Decision Drivers, Requirements, Options, Outcome, Goals, Migration Plan, Risks, Outstanding Decisions, Rationale) and includes a well-structured comparison table.

docs/adr-004-pluggable-protocol-codecs.md (1)

1-248: Excellent ADR: comprehensive documentation with implementation findings.

This ADR exemplifies the style guide's ADR template with:

  • All required sections present and well-structured
  • A five-phase migration plan with clear goals
  • Detailed implementation findings documenting codec ergonomics, error handling, and protocol handler experience
  • Well-formatted comparison table and code examples

The inclusion of real-world findings (Hotline/MySQL codec experiences) adds significant value for future maintainers.

docs/documentation-style-guide.md (2)

158-197: LGTM: Comprehensive roadmap formatting guidance.

The new roadmap conventions section provides clear, actionable guidance with:

  • Dotted numbering notation (phases/steps/tasks)
  • Checkbox tracking for completion status
  • Dependency citation patterns
  • A well-structured example demonstrating the hierarchy

This addition significantly improves documentation consistency.


198-309: LGTM: Thorough ADR documentation and template.

The new ADR section provides:

  • Clear naming conventions (adr-NNN-short-description.md)
  • Required sections (Status, Date, Context) and conditional sections appropriately distinguished
  • Formatting guidance including table captions and code block conventions
  • A complete, reusable template

This addition enables consistent architectural documentation across the project and aligns perfectly with the new ADRs introduced in this PR.

docs/execplans/pluggable-protocol-codecs.md (1)

1-241: No action required; the identified line-length violations are not prose paragraphs. Lines 9, 65, and 193 are HTML comments with decorative formatting (═══ separators) used as section dividers, and line 178 is code within a fenced block at 83 characters (compliant with the 120-column code block limit). The prose content in the document is correctly wrapped.

docs/roadmap.md (1)

381-474: Well-structured roadmap addition.

Phase 7.6 follows the established roadmap format, provides clear deliverables with appropriate cross-references to design documents, and aligns with the pluggable codec architecture introduced in this PR.

examples/mysql_codec.rs (1)

1-5: Clear example demonstrating pluggable codec integration.

The module documentation explains the MySQL packet format concisely, and the main function demonstrates the codec wiring pattern consistently with other examples.

Also applies to: 139-146

src/server/config/preamble.rs (2)

18-27: Generic bounds correctly propagate codec parameterisation.

The expanded impl block properly constrains Ser, Ctx, E, and Codec with appropriate trait bounds, aligning with the pluggable codec architecture throughout the server configuration.


47-63: Type transformation preserves all fields correctly.

The with_preamble method properly constructs the new WireframeServer variant, resetting preamble handlers as documented whilst preserving the _app phantom marker.

docs/users-guide.md (3)

104-109: Documentation clearly explains codec configuration options.

The updated text accurately describes the default length-delimited codec, configuration via buffer_capacity, codec swapping with with_codec, and serializer customisation with with_serializer. The FrameCodec trait mention guides implementers appropriately.


113-118: Connection handling documentation updated with codec-aware helpers.

The description of send_response_framed_with_codec and the "length-delimited by default" framing behaviour provides clear guidance for custom codec users.


342-347: Fragmentation documentation reflects codec-derived configuration.

The updated text correctly explains that FragmentationConfig now derives from the active frame codec's max_frame_length, maintaining consistency with the implementation.

tests/frame_codec.rs (2)

117-168: Comprehensive round-trip integration test.

The test exercises the full codec lifecycle: app construction, frame encoding, connection handling, response decoding, and correlation ID preservation. The assertions verify payload integrity and trailing byte absence.


1-2: Clear test module demonstrating FrameCodec implementation.

The TaggedFrameCodec implementation covers all trait methods and serves as a reference for custom codec authors. The module documentation states the purpose succinctly.

Also applies to: 97-115

src/app/frame_handling.rs (3)

51-62: ResponseContext correctly parameterised with codec marker.

The PhantomData<F> field properly carries the FrameCodec type through the context without runtime overhead, enabling send_response_payload to access F::wrap_payload for frame construction.


184-202: Generic frame wrapping via FrameCodec trait.

The renamed send_response_payload function correctly uses F::wrap_payload(payload) to construct codec-specific frames before sending, maintaining error handling and metrics instrumentation.


93-140: Response forwarding properly abstracted over codec types.

The forward_response function correctly threads codec generics through the call chain, maintaining fragmentation, serialization, and error handling behaviour whilst enabling custom frame types.

src/server/connection.rs (3)

21-33: Connection spawning correctly parameterised over codec and serializer.

The expanded generic bounds on spawn_connection_task properly constrain Ser: Serializer + FrameMetadata<Frame = Envelope>, ensuring the serializer can parse frame metadata for routing whilst supporting pluggable codecs.


55-86: Stream processing correctly delegates to generic WireframeApp.

The process_stream function properly invokes the factory-produced WireframeApp<Ser, Ctx, E, Codec> whilst maintaining preamble handling, error logging, and stream rewinding behaviour.


183-188: Tests updated with explicit type annotations for expanded generics.

The PreambleHooks::<()>::default() syntax correctly resolves type inference where the expanded generic signature prevents automatic deduction.

Also applies to: 238-243

examples/hotline_codec.rs (2)

1-6: Well-documented Hotline codec example.

The module documentation clearly explains the 20-byte header format, and the FrameCodec implementation provides a complete reference for protocol-aware framing. The main function demonstrates the wiring pattern concisely.

Also applies to: 111-134, 136-143


45-79: Decoder correctly implements Hotline framing with proper validation.

The implementation validates total_size consistency, enforces max_frame_length, and only consumes the buffer after confirming frame completeness. Error kinds are appropriately distinguished between InvalidData and insufficient data (returning Ok(None)).

src/server/mod.rs (2)

13-18: LGTM: Import organisation is appropriate.

The imports are well-organised, grouping crate-internal types logically. The addition of Envelope, Packet, WireframeApp, FrameCodec, LengthDelimitedFrameCodec, BincodeSerializer, and Serializer correctly supports the expanded generic wiring.


107-156: LGTM: Generic expansion and PhantomData usage are correct.

The struct signature correctly introduces Ser, Ctx, E, and Codec generics with sensible defaults (BincodeSerializer, (), Envelope, LengthDelimitedFrameCodec) preserving backward compatibility. The PhantomData<(Ser, Ctx, E, Codec)> field appropriately carries type information without runtime cost, and the trait bounds are correctly specified in the where clause.

src/server/config/mod.rs (3)

17-22: LGTM: Imports correctly expanded for generic wiring.

The addition of Packet, WireframeApp, FrameCodec, and Serializer imports properly supports the genericised server configuration.


52-91: LGTM: Constructor impl correctly parameterised.

The impl<F, Ser, Ctx, E, Codec> WireframeServer<F, (), Unbound, Ser, Ctx, E, Codec> block with the new() constructor correctly:

  • Specifies trait bounds matching the struct definition.
  • Returns a factory that produces WireframeApp<Ser, Ctx, E, Codec>.
  • Initialises _app: PhantomData for type tracking.

94-167: LGTM: Builder methods impl correctly propagates generics.

The second impl block correctly extends to WireframeServer<F, T, S, Ser, Ctx, E, Codec> with consistent trait bounds. Builder methods (workers, ready_signal, accept_backoff, worker_count) return Self, preserving the full generic signature through the fluent API.

src/codec.rs (6)

1-5: LGTM: Module documentation is clear and appropriate.

The module-level doc comment explains the purpose (pluggable framing codecs) and notes the default implementation's backward compatibility with length-prefixed format.


12-17: LGTM: Frame length constants and clamping helper are well-defined.

The MIN_FRAME_LENGTH (64 bytes) and MAX_FRAME_LENGTH (16 MiB) provide sensible guardrails. The clamp_frame_length() function correctly uses the standard library's clamp() method.


19-46: LGTM: FrameCodec trait design is sound.

The trait correctly:

  • Uses Send + Sync + 'static bounds for thread-safe usage.
  • Defines an associated Frame type with matching bounds.
  • Uses RPIT (impl Decoder/impl Encoder) for flexibility without boxing.
  • Provides a sensible default for correlation_id() returning None.
  • Documents methods with clear purpose descriptions.

48-77: LGTM: LengthDelimitedFrameCodec implementation is correct.

The struct:

  • Clamps max_frame_length on construction, preventing misconfiguration.
  • Provides a sensible default of 1024 bytes.
  • Exposes max_frame_length() as a public accessor.

79-134: LGTM: Adapter and FrameCodec implementation are correct.

The LengthDelimitedAdapter correctly:

  • Wraps tokio_util::codec::LengthDelimitedCodec.
  • Converts Bytes to Vec<u8> in the decoder.
  • Converts Vec<u8> to Bytes in the encoder.

The FrameCodec implementation for LengthDelimitedFrameCodec is straightforward, with frame_payload and wrap_payload being identity operations for Vec<u8>.


136-184: LGTM: Unit tests provide adequate coverage.

The tests correctly:

  • Verify max frame length clamping behaviour.
  • Test round-trip encode/decode for payloads.
  • Confirm oversized payload rejection with appropriate error kind.
  • Use .expect() as per coding guidelines for test code.
src/server/runtime.rs (4)

24-30: LGTM: Imports correctly expanded for generic runtime wiring.

The addition of Envelope, Packet, WireframeApp, FrameCodec, FrameMetadata, and Serializer properly supports the expanded generic signatures in the runtime module.


162-170: LGTM: WireframeServer Bound impl correctly parameterised.

The impl block correctly:

  • Introduces Ser, Ctx, E, Codec generics with appropriate bounds.
  • Adds FrameMetadata<Frame = Envelope> bound to Ser, ensuring the serializer can parse envelope metadata.
  • Maintains the Bound state requirement for run() and run_with_shutdown().

379-390: LGTM: accept_loop correctly propagates generic parameters.

The function signature correctly carries Ser, Ctx, E, Codec generics with matching trait bounds, enabling the accept loop to work with any compliant codec and serializer combination.


423-436: LGTM: accept_iteration correctly parameterised.

The internal iteration function mirrors the generic structure of accept_loop, maintaining type safety throughout the accept cycle.

src/server/config/binding.rs (5)

19-37: LGTM: WireframeFactory trait alias reduces bound repetition.

The helper trait alias and its blanket impl correctly encapsulate the factory function signature with all required bounds, reducing duplication across the three impl blocks.


43-43: LGTM: BoundServer type alias improves readability.

The BoundServer<F, T, Ser, Ctx, E, Codec> alias usefully shortens the return types for binding methods whilst preserving full type information.


50-96: LGTM: bind_to_listener correctly propagates generic parameters.

The internal binding method correctly:

  • Destructures and carries _app and _preamble phantom data through the binding process.
  • Returns BoundServer with all generic parameters preserved.
  • Handles the set_nonblocking and from_std operations with proper error mapping.

104-178: LGTM: Unbound impl correctly parameterised.

The impl block for Unbound state:

  • Correctly returns BoundServer from bind() and bind_existing_listener().
  • Provides local_addr() returning None for unbound servers.
  • Maintains the generic parameter chain through binding operations.

186-262: LGTM: Bound impl supports rebinding.

The impl block for Bound state correctly:

  • Returns Self from rebinding methods, preserving the full type.
  • Provides local_addr() that queries the listener.
  • Enables rebinding to new addresses whilst maintaining type safety.
src/app/connection.rs (7)

39-73: LGTM: CombinedCodec adapter is clean and correct.

The CombinedCodec<D, E> struct correctly:

  • Wraps separate decoder and encoder instances.
  • Implements Decoder by delegating to the inner decoder.
  • Implements Encoder<Item> by delegating to the inner encoder.
  • Provides decode_eof delegation for proper stream termination handling.

75-88: LGTM: FrameHandlingContext correctly carries codec marker.

The addition of PhantomData<F> as codec_marker correctly ties the context to the generic FrameCodec type without runtime overhead.


104-129: LGTM: send_response correctly uses codec for framing.

The method correctly:

  • Serialises the message via the serializer.
  • Wraps the payload using F::wrap_payload().
  • Encodes the frame using self.codec.encoder().
  • Writes the encoded buffer to the stream.

131-153: LGTM: send_response_framed_with_codec provides flexible framing.

The new generic method correctly:

  • Accepts any encoder compatible with F::Frame.
  • Wraps the serialised payload and sends via the framed stream.
  • Returns appropriate SendError on failure.

155-190: LGTM: LengthDelimitedFrameCodec specialisation is appropriate.

The specialised impl correctly:

  • Provides length_codec() for constructing a LengthDelimitedCodec with the codec's max frame length.
  • Offers send_response_framed for the common case of Framed<W, LengthDelimitedCodec>.
  • Maintains backward compatibility for existing users of length-delimited framing.

280-290: LGTM: Frame length clamping with warning is appropriate.

The code correctly:

  • Clamps the requested frame length using clamp_frame_length().
  • Logs a warning when the requested length exceeds MAX_FRAME_LENGTH.
  • Reserves buffer capacity based on the clamped value.

395-418: LGTM: decode_envelope correctly extracts correlation ID from frame.

The method correctly:

  • Extracts payload using F::frame_payload(frame).
  • Assigns correlation_id from F::correlation_id(frame) when the envelope lacks one.
  • Passes correlation ID to handle_decode_failure for improved error logging.
src/app/builder.rs (7)

37-45: LGTM: default_fragmentation helper correctly derives config from frame budget.

The function correctly:

  • Clamps the frame budget using clamp_frame_length().
  • Computes max message size as a multiple of the frame budget.
  • Falls back to frame_budget itself if the multiplication overflows or produces zero.
  • Delegates to FragmentationConfig::for_frame_budget() for the actual config construction.

80-98: LGTM: WireframeApp struct correctly parameterised over FrameCodec.

The struct correctly:

  • Introduces F: FrameCodec = LengthDelimitedFrameCodec with a sensible default.
  • Replaces buffer_capacity with codec: F for flexible framing.
  • Updates protocol type to use F::Frame for consistency.

116-142: LGTM: Default impl correctly initialises codec and fragmentation.

The implementation correctly:

  • Creates the codec via F::default().
  • Extracts max_frame_length from the codec.
  • Derives fragmentation configuration using default_fragmentation().

188-221: LGTM: rebuild_with_params helper eliminates duplication.

The helper correctly:

  • Centralises field-by-field reconstruction for type parameter changes.
  • Resets routes to OnceCell::new() since route chains may depend on serializer/codec types.
  • Uses #[expect(clippy::too_many_arguments, ...)] with an appropriate reason.
  • Moves owned fields from self rather than cloning unnecessarily.

This addresses the code duplication issue raised in the PR comments.


223-236: LGTM: with_codec correctly swaps codec and resets dependent state.

The method correctly:

  • Requires S: Default to enable std::mem::take() on the serializer.
  • Computes new fragmentation from the codec's max frame length.
  • Resets protocol to None since the frame type may change.
  • Delegates to rebuild_with_params() for reconstruction.

404-413: LGTM: serializer method correctly uses rebuild helper.

The method correctly:

  • Requires F: Default to enable std::mem::take() on the codec.
  • Preserves protocol and fragmentation when only changing serializer.
  • Delegates to rebuild_with_params() for reconstruction.

433-448: LGTM: buffer_capacity specialisation preserves backward compatibility.

The specialised impl for LengthDelimitedFrameCodec correctly:

  • Clamps the capacity using clamp_frame_length().
  • Creates a new codec with the clamped capacity.
  • Updates fragmentation to match the new frame budget.

This preserves the original buffer_capacity() API for users of the default codec.

Comment thread docs/adr-004-pluggable-protocol-codecs.md Outdated
Comment thread docs/adr-005-serializer-abstraction.md Outdated
Comment thread docs/adr-005-serializer-abstraction.md Outdated
Comment thread docs/documentation-style-guide.md Outdated
Comment thread docs/execplans/pluggable-protocol-codecs.md Outdated
Comment thread docs/roadmap.md Outdated
Comment thread examples/hotline_codec.rs Outdated
Comment thread examples/mysql_codec.rs Outdated
Comment thread examples/mysql_codec.rs Outdated
Comment thread tests/frame_codec.rs Outdated
Extract combined codec, lifecycle, and middleware type helpers into
separate app submodules, reducing builder and connection sizes.

Move BackoffConfig and runtime tests into runtime submodules to keep
server runtime within the file size guideline.
Clarify that correlation currently lives in the serialised Envelope
payload, and outline where to merge header correlation on inbound
frames plus the codec API change needed for outbound headers.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/builder.rs (1)

1-402: File exceeds 400-line limit.

This file contains 402 lines, exceeding the 400-line maximum specified in the coding guidelines. Decompose the module or reduce code volume.

Removing the rebuild_with_params helper (as suggested in the past review) would save approximately 26 lines and bring the file comfortably under the limit.

♻️ Duplicate comments (4)
src/server/runtime.rs (1)

322-333: Generic complexity already flagged.

The verbose <F, T, L, Ser, Ctx, E, Codec> signature was addressed in a prior review suggesting a RuntimeApp trait to reduce repetition.

src/app/connection.rs (2)

41-53: PhantomData complexity already flagged.

A prior review suggested collapsing the generic C parameter and removing PhantomData<F> by fixing to a ConnectionCodec<F> type alias.


341-358: Missing regression test already flagged.

A prior review requested a test for handle_decode_failure with the new correlation_id parameter and threshold behaviour.

src/app/builder.rs (1)

146-175: Duplicate concern: remove rebuild_with_params to eliminate lint suppression and artificial Default bounds.

The past review correctly identifies that this helper introduces unnecessary complexity:

  • The clippy::too_many_arguments suppression can be avoided entirely by inlining the reconstruction.
  • The S: Default bound in with_codec (line 185) and F: Default bound in serializer (line 361) exist only to support std::mem::take, which is unnecessary with direct destructuring.
  • Removing the helper also reduces the file length (currently 402 lines, exceeding the 400-line limit).

Follow the detailed refactoring approach provided in the past review to inline the reconstructions in with_codec and serializer, eliminating this helper entirely.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3e0548 and 5ec1183.

📒 Files selected for processing (9)
  • src/app/builder.rs
  • src/app/combined_codec.rs
  • src/app/connection.rs
  • src/app/lifecycle.rs
  • src/app/middleware_types.rs
  • src/app/mod.rs
  • src/server/runtime.rs
  • src/server/runtime/backoff.rs
  • src/server/runtime/tests.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • src/app/lifecycle.rs
  • src/server/runtime/backoff.rs
  • src/app/combined_codec.rs
  • src/server/runtime/tests.rs
  • src/app/middleware_types.rs
  • src/app/mod.rs
  • src/app/connection.rs
  • src/server/runtime.rs
  • src/app/builder.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • src/app/lifecycle.rs
  • src/server/runtime/backoff.rs
  • src/app/combined_codec.rs
  • src/server/runtime/tests.rs
  • src/app/middleware_types.rs
  • src/app/mod.rs
  • src/app/connection.rs
  • src/server/runtime.rs
  • src/app/builder.rs
🧬 Code graph analysis (4)
src/server/runtime/tests.rs (3)
src/server/runtime.rs (1)
  • accept_loop (322-360)
src/server/test_util.rs (2)
  • bind_server (75-82)
  • free_listener (27-30)
src/server/runtime/backoff.rs (1)
  • default (26-31)
src/app/connection.rs (3)
src/codec.rs (13)
  • clamp_frame_length (15-17)
  • wrap_payload (37-37)
  • wrap_payload (131-131)
  • encoder (31-31)
  • encoder (125-127)
  • new (60-64)
  • new (84-90)
  • max_frame_length (45-45)
  • max_frame_length (68-68)
  • max_frame_length (133-133)
  • correlation_id (42-42)
  • frame_payload (34-34)
  • frame_payload (129-129)
tests/frame_codec.rs (7)
  • wrap_payload (110-110)
  • encoder (104-106)
  • new (27-27)
  • new (36-36)
  • max_frame_length (114-114)
  • correlation_id (112-112)
  • frame_payload (108-108)
src/app/combined_codec.rs (1)
  • new (12-12)
src/server/runtime.rs (5)
src/app/builder.rs (1)
  • serializer (358-367)
src/app/connection.rs (1)
  • serializer (173-173)
tests/middleware_order.rs (1)
  • serializer (94-94)
tests/fragment_transport.rs (1)
  • serializer (135-135)
src/server/config/tests.rs (2)
  • WireframeServer (73-73)
  • WireframeServer (204-206)
src/app/builder.rs (1)
src/codec.rs (7)
  • clamp_frame_length (15-17)
  • new (60-64)
  • new (84-90)
  • default (72-76)
  • max_frame_length (45-45)
  • max_frame_length (68-68)
  • max_frame_length (133-133)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (13)
src/app/combined_codec.rs (1)

1-40: LGTM!

Clean delegation adapter that composes a decoder and encoder for Framed streams. The implementation correctly forwards all trait methods without introducing additional state or logic.

src/app/middleware_types.rs (1)

1-22: LGTM!

Well-structured type aliases and trait definitions for middleware composition. The blanket implementation correctly bridges Transform to the Middleware trait.

src/app/mod.rs (1)

11-26: LGTM!

Good modular reorganisation. The public re-exports provide a clean API surface with lifecycle hooks and middleware types now in dedicated modules.

src/app/lifecycle.rs (1)

1-33: LGTM!

Clear lifecycle hook definitions with appropriate async bounds. The doc examples demonstrate correct Arc wrapping for the trait objects.

src/server/runtime/backoff.rs (1)

1-65: LGTM!

Well-designed configuration type with sensible defaults and robust normalisation. The normalised() method correctly handles misconfiguration by clamping and swapping values as needed.

src/server/runtime/tests.rs (2)

174-228: Backoff test coverage is good.

The test correctly validates exponential backoff behaviour with time control. The sequence [5ms, 10ms, 20ms] demonstrates doubling with the cap at max_delay.


34-92: Server lifecycle tests are well-structured.

Good use of rstest fixtures and oneshot channels for shutdown signalling. The timeout guards prevent tests from hanging indefinitely.

src/server/runtime.rs (2)

105-113: LGTM on the generic bounds for WireframeServer.

The expanded generic surface correctly threads serializer, context, envelope, and codec types through the server runtime.


3-6: Good modular extraction.

Splitting backoff and tests into submodules improves maintainability and keeps file sizes manageable.

src/app/connection.rs (4)

245-255: Good guardrail with informative warning.

The clamping logic and warning when max_frame_length exceeds MAX_FRAME_LENGTH provides clear feedback without silently truncating.


101-117: Clean generic framed response helper.

send_response_framed_with_codec correctly abstracts over arbitrary encoders compatible with the codec's frame type.


120-155: Ergonomic specialisation for LengthDelimitedFrameCodec.

The length_codec() and send_response_framed methods provide a convenient API for the default codec without requiring users to thread generic parameters.


360-383: Frame-aware envelope decoding is correct.

The use of F::frame_payload and F::correlation_id properly abstracts payload extraction across different codec implementations. Backfilling correlation_id when absent is a sensible default.

Comment thread src/server/runtime/tests.rs Outdated
Introduce a RESP framing example that parses simple strings,
integers, bulk strings, and arrays, and wire it behind the
feature flag.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca8d93c and 9366515.

📒 Files selected for processing (2)
  • Cargo.toml
  • examples/resp_codec.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • examples/resp_codec.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • examples/resp_codec.rs
Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up-to-date.
All crate versions specified in Cargo.toml must use SemVer-compatible caret requirements (e.g., some-crate = "1.2.3"). Wildcard (*) or open-ended inequality (>=) version requirements are strictly forbidden. Tilde requirements (~) should only be used where a dependency must be locked to patch-level updates for a specific, documented reason.

Files:

  • Cargo.toml
🔍 Remote MCP Deepwiki

Additional context relevant to reviewing PR #413 (pluggable protocol codecs)

  • Repo docs and generated pages confirm a new pluggable framing abstraction is consistent with project design: a FrameCodec trait + default LengthDelimitedFrameCodec are now first-class and exposed in the crate API (matches PR objective) — see "Overview" / Protocol Processing and "FrameProcessor" sections.
  • WireframeApp/WireframeServer are documented as builder/factory types and the project already uses generics for serializers/envelopes; the docs describe the existing builder patterns and factory usage, so making the app/server generic over a codec follows established patterns and will affect many impl signatures (expect widespread signature changes across app/server modules).
  • Examples + tests: repository contains examples and tests for custom codecs (echo/ping examples, testing utilities). The test infra (wireframe_testing) supports in-memory framed tests; adding codec integration tests (TaggedFrame, Hotline, MySQL, RESP) aligns with existing testing patterns and utilities.
  • Fragmentation / multi-packet streaming and correlation semantics are a cross-cutting concern in docs (roadmap, multi-packet streaming design); the PR’s alignment of fragmentation config to codec max_frame_length is consistent with documented roadmap and multi-packet design notes. Review should confirm fragmentation updates propagate correctly to existing fragmentation/reassembly subsystems.
  • Public API & compatibility points to check (per documentation and builder semantics):
    • WireframeApp generic parameterization (new F: FrameCodec) changes many public method signatures (with_codec, serializer, protocol(), protocol_hooks(), buffer_capacity specialization) — ensure backward-compatible defaults compile (LengthDelimitedFrameCodec default).
    • WireframeServer generics threaded through factory types and binding/runtime code (accept loop, spawn_connection_task) — verify factory closures and typestate transitions still ergonomic and documented defaults preserved.
    • New public exports: codec module and re-exports (FrameCodec, LengthDelimitedFrameCodec) — check crate docs and public API surface.
  • Testing & CI implications:
    • Repo has extensive test harnesses (rstest, cucumber, wireframe_testing). PR adds unit/integration tests for codecs and updates Cargo.toml with new test entries/features; verify CI will run these new tests and that feature flags (advanced-tests, cucumber-tests) are wired correctly.
  • Suggested focused review areas (based on repo docs + PR summary):
    • Consistency of trait bounds and defaults across impls (WireframeApp, WireframeServer, ConnectionActor, runtime/accept loop).
    • Correct preservation of existing behavior when using default LengthDelimitedFrameCodec (buffer_capacity/clamping, fragmentation defaulting).
    • Correct propagation of correlation_id and decode/error logging changes when frames are protocol-specific (docs emphasize correlation provenance).
    • Integration of new CombinedCodec and CombinedEncoder/Decoder usage with tokio_util framed streams and existing push/response paths.
    • Build/test changes: new examples and tests are compiled/run in CI; Cargo.toml/test entries and Makefile change (markdownlint) included — confirm CI runs them and feature gates behave as intended.

Sources:

  • Project documentation and API pages retrieved from the repository wiki/docs via Deepwiki_read_wiki_contents.
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test
🔇 Additional comments (8)
Cargo.toml (2)

139-177: LGTM!

The new example entries follow the established pattern with correct paths and feature gating via required-features = ["examples"]. The codec examples (hotline, mysql, resp) align with the PR's pluggable codec objectives.


181-189: LGTM!

The test entries are correctly configured. The cucumber test appropriately disables the standard harness and requires both feature flags. The loom concurrency test is properly gated behind advanced-tests.

examples/resp_codec.rs (6)

1-21: LGTM!

Clear module documentation explaining the RESP subset supported, and imports are appropriately minimal.


67-81: LGTM!

The encoder correctly validates frame size before reserving capacity and encoding.


83-104: LGTM!

The FrameCodec implementation correctly bridges RESP frames to the wireframe abstraction, with frame_payload extracting bulk string contents and wrap_payload constructing the appropriate frame type.


322-354: LGTM!

Encoding logic is correct. The to_string() allocations mirror encoded_len; address there if optimizing.


356-359: LGTM!

Clean overflow-checked arithmetic helper.


361-368: LGTM!

Minimal but effective demonstration of wiring a custom codec into WireframeApp. The no-op handler and unused binding are appropriate for an illustrative example.

Comment thread examples/resp_codec.rs Outdated
Comment thread examples/resp_codec.rs Outdated
Comment thread examples/resp_codec.rs Outdated
Comment thread examples/resp_codec.rs Outdated
Comment thread examples/resp_codec.rs Outdated
Comment thread examples/resp_codec.rs Outdated
- expose codec adapter types needed by public associated types
- bound RESP parsing and allocation plus zero-copy length math
- align protocol tests with a Vec-backed codec and hooks
- split builder defaults and connection tests into modules
- refresh docs and examples for feature gating and spelling
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8aacc43 and 7d22507.

📒 Files selected for processing (3)
  • examples/resp_codec_impl/encode.rs
  • examples/resp_codec_impl/frame.rs
  • examples/resp_codec_impl/parse.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • examples/resp_codec_impl/parse.rs
  • examples/resp_codec_impl/encode.rs
  • examples/resp_codec_impl/frame.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • examples/resp_codec_impl/parse.rs
  • examples/resp_codec_impl/encode.rs
  • examples/resp_codec_impl/frame.rs
🧬 Code graph analysis (1)
examples/resp_codec_impl/parse.rs (3)
src/frame/format.rs (1)
  • bytes (54-54)
examples/resp_codec_impl/codec.rs (3)
  • max_frame_length (90-90)
  • new (24-24)
  • new (34-34)
src/app/frame_handling.rs (4)
  • max_frame_length (308-308)
  • new (31-31)
  • new (228-228)
  • new (237-237)
🔍 Remote MCP Deepwiki

Summary of additional facts relevant to reviewing PR #413 (pluggable-protocol-codecs)

  • New codec abstraction

    • Adds FrameCodec trait and default LengthDelimitedFrameCodec (public, re-exported) with associated Frame, Decoder, Encoder, payload accessors, wrap_payload, correlation_id, max_frame_length.
  • WireframeApp / connection changes (public surface)

    • WireframeApp is now generic over F: FrameCodec (WireframeApp<S,C,E,F>); buffer_capacity replaced by codec: F; many public method signatures updated (with_codec, serializer, with_protocol, protocol()/protocol_hooks()). Specialized impl retains buffer_capacity(capacity) for LengthDelimitedFrameCodec.
    • Connection handling now uses F::Frame throughout: parsing uses F::frame_payload(frame) and F::correlation_id(frame); decode path and error logging pass correlation_id. New public methods: send_response_framed_with_codec and specialized send_response_framed for length-delimited.
  • Server / factory generics

    • WireframeFactory and WireframeServer signatures expanded: factories must return WireframeApp<Ser, Ctx, E, Codec>; WireframeServer gains Ser, Ctx, E, Codec generics (defaults provided). Many bind/run APIs adjusted accordingly.
  • Framing plumbing

    • CombinedCodec wrapper (ConnectionCodec alias) pairs per-connection Decoder/Encoder so Framed uses ConnectionCodec. Framing uses ConnectionCodec and reserves/clamps frame lengths; logs guardrail warnings when exceeded. Fragmentation defaults derive from codec.max_frame_length via default_fragmentation helper.
  • Tests, examples, and examples-codecs

    • Added example codecs and examples: Hotline, Mysql, RESP (full example with parse/encode/frame), Tagged test codec; examples under examples/*.rs and src/codec/examples.rs. Integration/unit tests added (tests/frame_codec.rs, tests/example_codecs.rs, tests/wireframe_protocol.rs, connection & framing tests) exercising custom FrameCodec behavior and end-to-end round trips.
  • Resp encoder dependency

    • Cargo.toml adds dependency itoa = "1.0" (used by RESP encoder).
  • BackoffConfig relocation

    • BackoffConfig moved to src/server/runtime/backoff.rs, re-exported publicly; defaults initial_delay=10ms, max_delay=1s, with normalised() helper. Accept-loop/backoff behavior updated to use it.
  • Public/type-safety/complexity notes to verify in review

    • Many public signatures changed (WireframeApp, WireframeServer, ConnectionApp trait, ResponseContext types). Confirm intended semver/API compatibility.
    • Large expansion of generics across server/runtime/connection — reviewer comments in PR suggest potentially simplifying by using a single App trait/type; rebuild_with_params helper remains as an architectural choice.

Sources

  • Repository documentation and code overview: Deepwiki read of leynos/wireframe contents
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test
🔇 Additional comments (15)
examples/resp_codec_impl/frame.rs (1)

1-17: LGTM!

Clean, well-documented enum definition. The nullable semantics using Option for BulkString and Array correctly model RESP null values ($-1 and *-1).

examples/resp_codec_impl/encode.rs (5)

15-47: LGTM!

Correct length calculation with overflow protection via checked_add and recursion depth limiting. The hardcoded length of 5 for null variants ($-1\r\n, *-1\r\n) is accurate.


49-98: LGTM!

Encoding logic correctly implements the RESP wire format. Good use of itoa::Buffer for efficient integer formatting without heap allocation.


109-117: LGTM!

The digit-counting logic correctly handles edge cases including i64::MIN and negative values. The symmetric loop condition cursor <= -10 || cursor >= 10 elegantly handles both polarities.


100-107: LGTM!

Simple and correct digit-counting implementation.


119-122: LGTM!

Concise overflow-safe addition helper with appropriate error handling.

examples/resp_codec_impl/parse.rs (9)

51-57: LGTM!

Clean public entry point for incremental parsing.


59-72: LGTM!

Correct offset handling and UTF-8 validation.


74-86: LGTM!

Straightforward simple string parsing.


88-101: LGTM!

Correct integer parsing with proper error handling for invalid values.


103-163: LGTM!

Robust bulk string length parsing with correct null handling ($-1), rejection of invalid negative lengths, and overflow protection.


166-212: LGTM!

Payload parsing correctly validates frame length bounds before allocation and explicitly verifies CRLF termination.


214-270: LGTM!

Correct array parsing with null array support (*-1), element count limiting against allocation DoS, and proper recursion depth tracking. The final frame length check (line 263) ensures cumulative array size respects bounds.


272-297: LGTM!

Correct recursion depth limiting and prefix dispatch. The missing Error (-) prefix is flagged separately as a documentation issue.


299-325: LGTM!

Correct incremental line parsing with length limiting and proper handling of incomplete CRLF sequences.

Comment thread examples/resp_codec_impl/parse.rs
- Add Error(String) variant to RespFrame enum for `-` prefixed frames
- Add parse_error function and wire `-` branch into parse_frame_at
- Update encoder to handle Error variant (combined length calc with
  SimpleString, separate encoding for different prefix byte)
- Documentation table now matches implemented parser behaviour

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 2, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

examples/resp_codec_impl/parse.rs

Comment on lines +74 to +86

fn parse_simple_string(
    buf: &BytesMut,
    start: usize,
    max_frame_length: usize,
) -> Result<Option<(RespFrame, usize)>, io::Error> {
    let Some((text, next)) =
        parse_text_line(buf, start, max_frame_length, "invalid simple string")?
    else {
        return Ok(None);
    };
    let frame = RespFrame::SimpleString(text.to_string());
    Ok(Some((frame, next - start)))
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: parse_error,parse_simple_string

@coderabbitai

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d22507 and 593d452.

📒 Files selected for processing (3)
  • examples/resp_codec_impl/encode.rs
  • examples/resp_codec_impl/frame.rs
  • examples/resp_codec_impl/parse.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • examples/resp_codec_impl/encode.rs
  • examples/resp_codec_impl/parse.rs
  • examples/resp_codec_impl/frame.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • examples/resp_codec_impl/encode.rs
  • examples/resp_codec_impl/parse.rs
  • examples/resp_codec_impl/frame.rs
🔍 Remote MCP Deepwiki

Summary of additional, review-relevant facts (from repository docs/code index) — key points to check while reviewing PR #413:

  • New public FrameCodec abstraction and default implementation

    • Adds trait FrameCodec (associated Frame, Decoder, Encoder) and LengthDelimitedFrameCodec (default, constructor, max_frame_length) exported from crate root.
  • WireframeApp / connection API become codec-generic

    • WireframeApp gains a new generic F: FrameCodec (WireframeApp<S,C,E,F>); buffer_capacity replaced by codec: F; many builder and accessor signatures changed (with_codec, serializer, with_protocol, protocol()/protocol_hooks()). Connection handling now uses F::Frame and F::frame_payload / F::correlation_id.
  • WireframeServer and factory wiring become codec-generic

    • WireframeFactory trait and WireframeServer types expanded to include Ser, Ctx, E, Codec generics; factories now must return WireframeApp<Ser, Ctx, E, Codec>. This substantially expands public/type-level surface area.
  • Framing plumbing / per-connection codec adapter

    • Added CombinedCodec / ConnectionCodec wrapper used with tokio_util::codec::Framed to pair per-connection decoder+encoder. Framed streams now use ConnectionCodec.
  • New send helpers and frame-aware paths

    • New public methods: send_response_framed_with_codec (generic) and a specialized send_response_framed for LengthDelimitedFrameCodec; send path now uses F::wrap_payload(payload). decode path uses F::frame_payload(frame).
  • Examples, test coverage and example codecs added

    • New example codecs and examples: Hotline, Mysql, RESP (full parse/encode), Tagged test codec; integration and unit tests added (tests/frame_codec.rs, tests/example_codecs.rs, tests/wireframe_protocol.rs, connection & framing tests) exercising custom FrameCodec round-trips, correlation-id propagation, decode-failure logging, and send_response_payload semantics.
  • Resp encoder dependency added

    • Cargo.toml now adds itoa = "1.0" (used by RESP encoder).
  • BackoffConfig extracted and public

    • BackoffConfig moved to src/server/runtime/backoff.rs, public with defaults (initial_delay = 10ms, max_delay = 1s) and normalised() helper; accept-loop uses it.
  • Fragmentation/defaults now derive from codec

    • Default fragmentation computation uses codec.max_frame_length() (via default_fragmentation helper); specialized buffer_capacity exists only for LengthDelimitedFrameCodec.
  • Public API surface & semver risk

    • Many public types/signatures changed (WireframeApp, WireframeServer, ConnectionApp trait, ResponseContext types, new public methods). These are likely breaking changes for consumers; verify intended semantic/versioning impact.
  • Notable reviewer-discussed design choices still present

    • rebuild_with_params helper (used when swapping serializer/codec) remains (author added it after duplication feedback); WireframeFactory retains verbose bounds — both noted as outstanding architectural choices in PR comments.

Sources: Deepwiki repository index and documentation/code summaries extracted from leynos/wireframe.

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (5)
examples/resp_codec_impl/frame.rs (1)

1-19: LGTM!

Clean data model for RESP frames. The enum variants correctly represent all RESP2 types with appropriate nullable representations for bulk strings and arrays.

examples/resp_codec_impl/encode.rs (1)

1-129: LGTM!

Well-structured encoder with proper recursion depth limits and overflow protection via checked_add. The separation between public API (encoded_len, encode_frame) and internal depth-tracking implementations is clean.

examples/resp_codec_impl/parse.rs (3)

1-31: LGTM!

Comprehensive module documentation that clearly explains the RESP protocol, parser behaviour, supported types, and integration context. The DoS protections (MAX_ARRAY_ELEMENTS, MAX_RECURSION_DEPTH) are well-documented with security rationale.


226-282: LGTM!

Null array handling (*-1\r\n) is correctly implemented per the RESP specification. The element count limit and cumulative byte validation provide robust DoS protection.


284-310: LGTM!

All five RESP frame types are now handled, including the error prefix (-). Recursion depth enforcement is correctly placed before any parsing work.

Comment thread examples/resp_codec_impl/parse.rs Outdated
Introduce generic parse_text_frame helper that parameterises the frame
constructor, eliminating duplicate parse-and-wrap logic between
parse_simple_string and parse_error functions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 2, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

examples/resp_codec_impl/parse.rs

Comment on lines +96 to +108

fn parse_simple_string(
    buf: &BytesMut,
    start: usize,
    max_frame_length: usize,
) -> Result<Option<(RespFrame, usize)>, io::Error> {
    parse_text_frame(
        buf,
        start,
        max_frame_length,
        "invalid simple string",
        RespFrame::SimpleString,
    )
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: parse_error,parse_simple_string

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 2, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

examples/resp_codec_impl/parse.rs

Comment on lines +79 to +94

fn parse_text_frame<F>(
    buf: &BytesMut,
    start: usize,
    max_frame_length: usize,
    error_msg: &str,
    constructor: F,
) -> Result<Option<(RespFrame, usize)>, io::Error>
where
    F: FnOnce(String) -> RespFrame,
{
    let Some((text, next)) = parse_text_line(buf, start, max_frame_length, error_msg)? else {
        return Ok(None);
    };
    let frame = constructor(text.to_string());
    Ok(Some((frame, next - start)))
}

❌ New issue: Excess Number of Function Arguments
parse_text_frame has 5 arguments, max arguments = 4

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 2, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the current state of parse.rs to examine the duplication
cat examples/resp_codec_impl/parse.rs

Length of output: 11523


@leynos: The duplication flagged by CodeScene is structural rather than logical and is acceptable in this case. The shared parsing logic has already been extracted into parse_text_frame — the thin wrappers parse_simple_string and parse_error serve as necessary entry points for different RESP frame types.

Why This Duplication Is Acceptable

  1. Shared logic already extracted: Both functions delegate to parse_text_frame, which contains all the common parsing logic. The only differences are:

    • Error message: "invalid simple string" vs "invalid error"
    • Frame constructor: RespFrame::SimpleString vs RespFrame::Error
  2. Necessary dispatch structure: parse_frame_at switches on the prefix byte (b'+' vs b'-') and calls the appropriate function. These separate entry points make the dispatch logic clear and maintain symmetry with the other frame parsers (parse_integer, parse_bulk_string, parse_array).

  3. Readability over DRY: Further abstraction (e.g., inlining into parse_frame_at with match arms, or using a macro) would reduce clarity without eliminating meaningful duplication.

  4. Example code context: This is in examples/resp_codec_impl rather than core library code, where demonstrating clarity and pattern consistency is more valuable than maximum abstraction.

Recommendation

Accept this duplication. The pattern of thin wrapper functions delegating to a shared helper with different parameters is a valid design choice. Static analysis tools often flag structural similarity even when further refactoring would harm readability.

If you want to silence the CodeScene warning specifically, you could:

  • Add a suppression comment for this specific duplication pattern
  • Configure CodeScene's duplication threshold to ignore functions below a certain complexity
  • Accept the finding as a false positive in CodeScene's review UI

However, I would not recommend further refactoring here — the code is already well-factored for its purpose.

🐰

    /)/)
   (•.•)  Sometimes thin wrappers
   />📋  are the right abstraction!

@coderabbitai

This comment was marked as resolved.

Add Copy-able ParseContext struct that groups buf, start, and
max_frame_length parameters. Update parse_text_line, parse_text_frame,
and parse_integer to use the new context struct, removing the need for
the clippy::too_many_arguments suppression.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Update parse_bulk_payload to calculate total frame size including
header ('$' + digits + CRLF) and trailing CRLF, rather than just
comparing payload length against max_frame_length.

Add decimal_digits helper function for computing digit count.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 2, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
examples/resp_codec_impl/parse.rs (1)

100-139: Duplication adequately addressed by parse_text_frame helper.

The CodeScene warning about duplication between parse_simple_string and parse_error is a false positive in this context. Both functions are minimal wrappers (7 lines each) delegating to the shared parse_text_frame helper, which centralises the parsing logic. This is the idiomatic pattern for type-specific constructors with shared behaviour.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 593d452 and 283efac.

📒 Files selected for processing (1)
  • examples/resp_codec_impl/parse.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • examples/resp_codec_impl/parse.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • examples/resp_codec_impl/parse.rs
🔍 Remote MCP Deepwiki

Summary — additional review-relevant facts found

  • Repository docs and API consistently describe Wireframe as actor-per-connection, length-delimited default framing, pluggable serializer, push queues, and session registry — the PR’s FrameCodec + codec-generic app/server changes align with existing extension points (WireframeProtocol, PushHandle, SessionRegistry).

  • WireframeServer/ WireframeApp builders and factory pattern are core to runtime; server factories are expected to produce per-worker WireframeApp instances — changing the factory bound to return WireframeApp<Ser, Ctx, E, Codec> (as this PR does) materially expands the type-level surface the server APIs require callers to satisfy. This is a likely semver-breaking surface to highlight.

  • The docs emphasise fragmentation, max-message-size, and fragmentation-derived defaults; the PR’s change to derive fragmentation and buffer sizes from codec.max_frame_length follows that documented intent (so reviewers should verify clamp/guardrail behavior and default_fragmentation correctness).

  • The project has extensive tests and testing utilities (wireframe_testing) relying on in-memory duplex streams and deterministic time advancement. The PR added codec tests and integration tests (tagged examples and tests/frame_codec.rs); reviewers should ensure new codec-specific tests use the same test helpers and that CI test matrix (features like "examples", "advanced-tests") remains compatible.

  • Observability/metrics and panic-isolation are documented expectations (connection panics must be isolated); the PR threads correlation_id into decode-failure logs and decode paths — reviewers should verify log messages and panic-isolation behavior remain intact.

Citations

  • Repository docs/index and API pages: Deepwiki read of leynos/wireframe wiki contents.
🔇 Additional comments (6)
examples/resp_codec_impl/parse.rs (6)

1-31: Documentation is comprehensive and well-structured.

The module doc clearly explains RESP, supported types, null handling semantics, and the parser's role within the example codec. Good work addressing the previous review feedback.


38-59: DoS protections and digit counting are correct.

The constants provide reasonable limits against allocation-based and stack-exhaustion attacks. The decimal_digits helper correctly handles zero and counts digits efficiently.


61-77: Good use of parameter grouping.

ParseContext reduces function argument counts and improves readability, aligning with the coding guidelines for grouping related parameters.


219-251: Bulk payload parsing correctly validates total frame size.

The calculation at lines 224-227 properly accounts for header overhead ($ + digits + \r\n) plus payload plus trailing \r\n. The overflow protection via checked_add is appropriate. Previous review feedback has been addressed.


271-327: Array parsing is robust with proper safeguards.

Null arrays are correctly handled (line 286-288), element count is bounded (line 300-305), recursion depth is tracked, and total consumed bytes are validated against max_frame_length (line 320-325). Good defensive coding.


329-355: Dispatch logic is correct and complete.

The recursion depth check precedes any parsing work, and all documented RESP prefixes are handled with appropriate fallback for unknown prefixes.

Comment thread examples/resp_codec_impl/parse.rs
Change comparison from >= to > so lines with exactly max_len bytes are
accepted. The error should only trigger when length strictly exceeds
the limit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates Failed
Enforce advisory code health rules (1 file with Code Duplication)

Gates Passed
5 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
parse.rs 1 advisory rule 9.39 Suppress

Quality Gate Profile: Pay Down Tech Debt
Want more control? Customize Code Health rules or catch issues early with our IDE extension and CLI tool.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 283efac and 1e1dd59.

📒 Files selected for processing (1)
  • examples/resp_codec_impl/parse.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • examples/resp_codec_impl/parse.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • examples/resp_codec_impl/parse.rs
🧬 Code graph analysis (1)
examples/resp_codec_impl/parse.rs (5)
src/frame/format.rs (1)
  • bytes (54-54)
src/request.rs (1)
  • value (192-197)
examples/resp_codec_impl/codec.rs (3)
  • new (24-24)
  • new (34-34)
  • max_frame_length (90-90)
src/app/frame_handling.rs (4)
  • new (31-31)
  • new (228-228)
  • new (237-237)
  • max_frame_length (308-308)
src/fragment/index.rs (1)
  • try_from (48-48)
🔍 Remote MCP Deepwiki

Summary of additional review-relevant facts (concise)

  • Public API / semver impact

    • WireframeApp and WireframeServer builder types are documented as core public APIs; changing WireframeApp/WireframeServer generics (adding codec type parameters and changing factory bounds to produce WireframeApp<Ser, Ctx, E, Codec>) affects the public factory/constructor types and is semver-sensitive.
  • Defaults and backwards-compatibility expectations

    • The project documents a length-delimited framing default and BincodeSerializer default; PR preserves a length-delimited default via LengthDelimitedFrameCodec but threads codec through many builder signatures — reviewers should confirm defaults remain ergonomic and that the LengthDelimitedFrameCodec::default matches prior buffer_capacity behaviour.
  • Fragmentation / max-frame-size behavior

    • Documentation and code rely on fragmentation and buffer sizing derived from codec max_frame_length; ensure clamp/guardrail constants and default_fragmentation computation preserve existing DoS protections and default fragmentation (previous buffer_capacity→now codec.max_frame_length).
  • Connection/server runtime changes

    • Accept loop and spawn_connection_task are documented to use factory->WireframeApp instances per worker. PR changes make accept_loop/process_stream generic over an App/ConnectionApp; verify worker-factory expectations and that panic-isolation and BackoffConfig behaviour remain unchanged (BackoffConfig defaults: initial 10ms, max 1s; normalised() clamps to ≥1ms).
  • Testing and CI implications

    • The repository uses extensive test helpers (wireframe_testing) and feature-gated advanced tests (cucumber, loom). PR adds many codec-specific tests and new example/features — reviewers should check test feature flags (examples, advanced-tests, cucumber-tests) and CI matrix to ensure new tests run under CI and harness changes (new [[test]] entries) are compatible.
  • Observability / error logging expectations

    • Docs require correlation_id propagation and structured logging; PR threads correlation_id into decode-failure logs and decode paths — confirm log messages and metrics (e.g., connection panics metric) follow existing conventions.

Sources:

  • Deepwiki repository docs and API pages used for above facts.
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (8)
examples/resp_codec_impl/parse.rs (8)

1-31: LGTM: Documentation is comprehensive.

The module-level documentation clearly explains the RESP protocol, supported types, incremental parsing behaviour, and the module's role within the example codec.


38-59: LGTM: DoS guards and helper are sound.

The constants provide reasonable bounds for allocation and recursion protection. The decimal_digits helper is clear and avoids floating-point arithmetic.


100-139: Static analysis duplication concern resolved.

The parse_text_frame helper (lines 100-115) extracts the shared logic, and parse_simple_string/parse_error are now thin wrappers differing only in the error message and frame constructor. This addresses the CodeScene finding whilst maintaining readable, discoverable APIs.


141-154: LGTM: Integer parsing is correct.

The implementation follows the established pattern and handles parsing errors appropriately.


219-251: LGTM: Bulk payload length check now accounts for total frame size.

The calculation at lines 223-227 correctly includes the header ($ + digits + \r\n) and trailing terminator (\r\n) when validating against max_frame_length. The checked arithmetic guards against overflow.


271-327: LGTM: Array parsing with null support and DoS guards.

The null array handling (*-1\r\n) at lines 285-288 aligns with the RESP specification. The element count limit, recursion depth control, and cumulative byte validation provide layered protection against malicious input.


329-355: LGTM: Frame dispatch handles all documented types.

The recursion depth check fails fast, and all five documented RESP prefixes (+, -, :, $, *) are now handled—including the error prefix that was previously flagged as missing.


357-383: LGTM: Off-by-one fix applied.

The comparison at line 364 now uses > instead of >=, allowing lines with exactly max_len bytes of content whilst still rejecting oversized input.

Comment thread examples/resp_codec_impl/parse.rs
@leynos leynos merged commit 02a5cf0 into main Jan 2, 2026
4 of 5 checks passed
@leynos leynos deleted the pluggable-protocol-codecs branch January 2, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant