Skip to content

Refactor errors, buffer sizing, tests, and docs; add fixtures#456

Merged
leynos merged 14 commits intomainfrom
rename-payload-to-into-payload-q2jpyh
Feb 9, 2026
Merged

Refactor errors, buffer sizing, tests, and docs; add fixtures#456
leynos merged 14 commits intomainfrom
rename-payload-to-into-payload-q2jpyh

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Feb 6, 2026

Summary

  • Refactor errors, buffer sizing, tests, and docs across core framing, extraction, and server/config tests. Adds new test harnesses and fixtures (rstest), per-connection deserialization limits, and Eq/PartialEq derives for recovery configuration. Improves error messages and logging, and tightens client read-buffer reservations.

Changes

  • Documentation

    • docs/v0-1-0-to-v0-2-0-migration-guide.md: adjust wording to emphasize required breaking changes and migration clarity.
  • Core framework

    • src/app/frame_handling.rs:
      • Introduced DeserFailureTracker to track per-connection deserialization failures with a per-connection limit.
      • Added ResponseContext to carry serializer/codec state and framing context when writing handler responses.
      • Improved logging for handler, fragmentation, and serialization failures with structured formatting.
      • Propagated errors via standard error paths (using ? where appropriate) for clearer error handling.
  • Client builder

    • src/client/builder.rs:
      • Added INITIAL_READ_BUFFER_CAPACITY_LIMIT (64 KiB).
      • Initial read buffer capacity now uses min(INITIAL_READ_BUFFER_CAPACITY_LIMIT, codec_config.max_frame_length_value()) to avoid oversized allocations.
      • Documentation examples updated to reflect non-async closure usage in on_error examples.
  • Codec recovery tests

    • src/codec/recovery.rs:
      • Tests migrated to use rstest fixtures for default recovery policy, context, and IO error scenarios.
      • Verifies that DefaultRecoveryPolicy delegates to the correct RecoveryPolicy and that quarantine duration remains 30 seconds for IO errors.
      • RecoveryConfig derives PartialEq and Eq for easier comparison in tests.
  • Extractor error handling

    • src/extractor.rs:
      • Added thiserror::Error deriving for ExtractError and updated variants with descriptive messages.
      • Replaced manual Display and Error trait implementations with derive-based messages and proper source attribution for InvalidPayload.
      • Minor documentation/example text updates to reflect improved error messages and state extraction semantics.
  • Server tests and fixtures

    • src/server/config/tests.rs:
      • Added connected_streams() as an async fixture to create a pair of connected TCP streams.
      • Updated tests to use the new fixture (instead of manually creating sockets inline) and adjusted code paths to accommodate fixture return types.
      • Refactored several tests to use let bindings instead of mutable server instances and kept behavior consistent with new fixtures.
  • Miscellaneous

    • Documentation and code examples in extractor-related docs updated to reflect improved error handling and safer state access patterns (e.g., using expect when accessing shared state).
  • Test helpers and fixtures

    • src/test_helpers/frame_codec.rs: Minor doc comment updates to reflect usage context for TestFrame and TestCodec.

Test plan

  • Build the project to ensure all compilation units align with the refactors.
  • Run cargo test to validate unit tests and integration tests, including new rstest fixtures.
  • Verify that logging outputs remain clear and that error propagation paths are exercised by tests.

Review notes

  • The major areas touched include error handling (Extractor, frame handling), buffer sizing logic, and test fixtures for server/config tests. These changes improve error reporting, align with modern Rust error handling patterns, and stabilize tests that rely on network I/O.
  • Some changes (e.g., rstest fixtures) assume rstest is used in the workspace’s test suite; ensure CI includes the necessary dev-dependencies.
  • Migration guide wording tweak is intentionally minor but helps clarity for upgrades.

If you want me to adjust the PR description tone or add a short changelog-like bullet list, I can update accordingly.

📎 Task: https://www.devboxer.com/task/f39504b6-9fcc-4590-9bd4-ea21171e9d02

Summary by Sourcery

Refine server configuration defaults, error handling, and tests while tightening client read buffer sizing and improving extractor ergonomics and documentation.

Bug Fixes:

  • Prevent deserialization failure counters from overflowing by using saturating increments and enforce per-connection limits.
  • Clamp initial client read buffer reservations to a bounded maximum relative to codec frame length to avoid oversized allocations.

Enhancements:

  • Unify server default worker-count calculation behind a shared helper and simplify server worker configuration tests.
  • Propagate frame handling and response serialization errors via standard io::Result instead of swallowing them, with clearer structured logging for handler, fragmentation, and serialization failures.
  • Introduce a reusable harness and fixtures for framed I/O tests and recovery policy tests using rstest, reducing duplication and improving coverage of backoff scenarios.
  • Derive PartialEq and Eq for recovery configuration to allow direct comparison in tests and make extractor errors thiserror::Error with better display messages and sources.
  • Add an internal helper to rebuild apps when changing connection state type, reducing duplication in lifecycle builder code.

Documentation:

  • Clarify migration guide wording around required breaking changes and update extractor and connection info examples to use safer patterns and more explicit expectations.

Tests:

  • Refactor server configuration tests to use a shared connected_streams fixture and expand backoff tests into table-driven scenarios covering clamping and parameter swapping.
  • Consolidate frame handling tests into a parameterized harness that verifies codec wrapping behaviour and failure paths without duplicating setup.
  • Introduce fixtures for default recovery policy, context, and IO error cases to validate delegation and quarantine duration semantics.

📎 Task: https://www.devboxer.com/task/51a5833d-284f-4bfe-9a6b-012cec4035fb

leynos and others added 4 commits February 6, 2026 02:11
- Introduces v0.1.0 to v0.2.0 migration guide detailing breaking changes
- Notes removal of deprecated payload() methods in favor of into_payload()
- Updates existing docs for minor formatting improvements
- Removes deprecated payload accessor methods from PacketParts and FragmentParts

These changes improve developer experience by documenting required migration steps and cleaning up deprecated APIs.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Move preamble tests into grouped modules under tests/preamble/\nwith shared helpers to keep file sizes under the limit.\n\nAdd a Makefile typecheck target to match the commit gates.
Address review feedback by clarifying the migration guide intro and\nkeeping payload-accessor documentation neutral.\n\nRefactor preamble tests to avoid excessive nesting and add helper\nRustdoc examples for shared test utilities.
Propagate response send errors and tidy logging output.
Update client builder docs and buffer sizing constant.
Refactor extractor errors/docs and server config tests to align
with fixture guidance.
Refresh migration guide wording and payload accessor example.
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Feb 6, 2026

Reviewer's Guide

Refactors error handling, buffer sizing, and tests across framing, extractor, client, and server components, tightening invariants, improving logging, and consolidating test coverage while making small documentation and API ergonomics tweaks.

Updated class diagram for extractor error handling and state access

classDiagram
    class MessageRequest {
        +Option<SocketAddr> peer_addr
        +Option<StreamingBody> body
        +HashMap<TypeId, Arc<dyn Any+Send+Sync>> app_data
        +new() MessageRequest
        +with_peer_addr(peer_addr: Option<SocketAddr>) MessageRequest
        +state<T>() Option<SharedState<T>>
        +insert_state<T>(state: T)
    }

    class SharedState~T~ {
        -Arc<T> inner
        +deref() &T
        +into_inner(self) T
    }

    class ExtractError {
        <<enum>>
        MissingState(ty: &'static str)
        InvalidPayload(source: bincode::error::DecodeError)
        MissingBodyStream
    }

    class ConnectionInfo {
        -Option<SocketAddr> peer_addr
        +peer_addr(&self) Option<SocketAddr>
        +from_message_request(req: &MessageRequest, payload: &mut Payload) Result<ConnectionInfo, ExtractError>
    }

    class Payload {
        +default() Payload
    }

    MessageRequest "1" o-- "*" SharedState : stores
    SharedState "1" --> "1" ExtractError : may produce
    ConnectionInfo --> MessageRequest : extracts_from
    ConnectionInfo --> Payload : uses
    ConnectionInfo --> ExtractError : error_type
Loading

Updated class diagram for app and client builders with timeouts and buffer limits

classDiagram
    class WireframeApp~S,C,E,F~ {
        +HashMap<RouteKey, Handler> handlers
        +OnceCell<Router> routes
        +Vec<Middleware> middleware
        +S serializer
        +HashMap<TypeId, Arc<dyn Any+Send+Sync>> app_data
        +Option<Arc<ConnectionSetup<C>>> on_connect
        +Option<Arc<ConnectionTeardown<C>>> on_disconnect
        +Option<ProtocolConfig> protocol
        +Option<PushDlq> push_dlq
        +F codec
        +u64 read_timeout_ms
        +Option<FragmentationConfig> fragmentation
        +Option<MessageAssembler> message_assembler
        +rebuild_with_connection_type~C2~(self, on_connect: Option<Arc<ConnectionSetup<C2>>>, on_disconnect: Option<Arc<ConnectionTeardown<C2>>>) WireframeApp~S,C2,E,F~
    }

    class ConnectionSetup~C~ {
        <<type_alias>>
        +call() Pin<Box<dyn Future<Output=C>+Send>>
    }

    class ConnectionTeardown~C~ {
        <<type_alias>>
        +call(ctx: C) Pin<Box<dyn Future<Output=()>+Send>>
    }

    class BuilderDefaults {
        <<module_consts>>
        +u64 MIN_READ_TIMEOUT_MS = 1
        +u64 MAX_READ_TIMEOUT_MS = 86400000
        +u64 DEFAULT_READ_TIMEOUT_MS = 100
    }

    class WireframeClientBuilder~S,Ctx,E,Codec~ {
        +CodecConfig codec_config
        +on_error(handler: fn(ClientError) -> impl Future<Output=()>+Send) WireframeClientBuilder~S,Ctx,E,Codec~
        +build_with_stream(stream: TlsOrTcpStream, leftover: Bytes) WireframeClient~S,Ctx,E,Codec~
    }

    class CodecConfig {
        +max_frame_length_value() usize
        +build_codec() Codec
    }

    class Framed~IO,Codec~ {
        +new(io: IO, codec: Codec) Framed~IO,Codec~
        +read_buffer_mut() &mut BytesMut
    }

    class RewindStream~IO~ {
        +new(leftover: Bytes, stream: IO) RewindStream~IO~
    }

    class ClientConsts {
        <<module_consts>>
        +usize INITIAL_READ_BUFFER_CAPACITY_LIMIT = 64*1024
    }

    BuilderDefaults <.. WireframeApp : uses_defaults
    WireframeApp --> ConnectionSetup : on_connect
    WireframeApp --> ConnectionTeardown : on_disconnect

    WireframeClientBuilder --> CodecConfig : has
    WireframeClientBuilder --> ClientConsts : uses
    WireframeClientBuilder --> Framed : constructs
    WireframeClientBuilder --> RewindStream : wraps
    CodecConfig --> Framed : configures
Loading

Updated class diagram for codec recovery configuration and policy hooks

classDiagram
    class RecoveryPolicy {
        <<enum>>
        Drop
        Disconnect
        Quarantine(Duration)
    }

    class CodecErrorContext {
        +Option<SocketAddr> peer_address
        +new() CodecErrorContext
        +set_peer_address(addr: SocketAddr)
    }

    class CodecError {
        <<enum>>
        Framing(FramingError)
        Io(io::Error)
        Eof(EofError)
        +default_recovery_policy(&self) RecoveryPolicy
    }

    class RecoveryPolicyHook {
        <<trait>>
        +recovery_policy(&self, error: &CodecError, ctx: &CodecErrorContext) RecoveryPolicy
        +quarantine_duration(&self, error: &CodecError, ctx: &CodecErrorContext) Duration
    }

    class DefaultRecoveryPolicy {
    }

    class RecoveryConfig {
        <<data>>
        +u32 max_consecutive_drops
        +Duration quarantine_duration
        +new() RecoveryConfig
    }

    RecoveryPolicyHook <|.. DefaultRecoveryPolicy : implements
    DefaultRecoveryPolicy --> CodecError : delegates_policy
    DefaultRecoveryPolicy --> CodecErrorContext : uses
    RecoveryConfig --> RecoveryPolicy : configures
Loading

File-Level Changes

Change Details Files
Stabilized server configuration tests with shared fixtures and consolidated backoff configuration scenarios.
  • Introduced an async connected_streams rstest fixture to create paired TCP streams for tests.
  • Refactored server tests to avoid mutable WireframeServer reuse and to assert worker counts via default_worker_count helper.
  • Replaced multiple accept_backoff tests with a single parameterized BackoffScenario-based test covering clamping and swapping semantics.
src/server/config/tests.rs
src/server/config/mod.rs
Hardened frame response handling with structured logging, explicit error propagation, and a deserialization failure tracker, plus more robust tests.
  • Added DeserFailureTracker with saturating increment and per-connection limit enforcement.
  • Switched handler, fragmentation, and serialization paths from manual error swallowing to ?-based propagation with clearer warn! logs using named arguments.
  • Refactored send_response_payload tests to use a FramedHarness and rstest cases that cover both success and oversized payload failures; added docs around ResponseContext usage.
src/app/frame_handling.rs
Improved codec recovery configuration ergonomics and testability via equality semantics and rstest-based fixtures.
  • Derived PartialEq and Eq for RecoveryConfig to simplify config assertions in tests.
  • Introduced rstest fixtures for DefaultRecoveryPolicy, CodecErrorContext, and common CodecError values.
  • Updated recovery policy tests to use fixtures and verify delegation and IO error quarantine duration via rstest.
src/codec/recovery.rs
Refined extractor API surface and error reporting using thiserror while tightening documentation around shared state and connection info.
  • Made MessageRequest.app_data pub(crate) to encapsulate internal state storage while leaving accessor APIs intact.
  • Derive thiserror::Error for ExtractError with variant-specific messages and proper source on InvalidPayload, replacing manual Display and Error implementations.
  • Updated examples to use expect(...) when accessing shared state/connection info and clarified documentation wording and spelling on deserialization.
src/extractor.rs
src/message.rs
Centralized app and client builder defaults and buffer sizing, improving configurability and preventing overly large initial allocations.
  • Introduced DEFAULT_READ_TIMEOUT_MS and used it for WireframeApp read_timeout_ms initialization instead of a magic number.
  • Added INITIAL_READ_BUFFER_CAPACITY_LIMIT and used min(limit, max_frame_length) when reserving the client's initial read buffer capacity.
  • Factored out a rebuild_with_connection_type helper on WireframeApp to reuse builder state when changing connection type and updated lifecycle hooks to call it; relaxed serializer bound by dropping Default on the LengthDelimitedFrameCodec specialization.
src/app/builder.rs
src/app/builder_lifecycle.rs
src/app/builder_defaults.rs
src/client/builder.rs
Polished docs and test helpers for clarity and alignment with the updated APIs.
  • Adjusted migration guide wording to say "breaking changes you need to address" for clearer actionability.
  • Clarified TestFrame documentation to mention TestCodec explicitly.
  • Updated client on_error example to use a non-async closure that returns an async block, matching actual type signatures and usage patterns.
docs/v0-1-0-to-v0-2-0-migration-guide.md
src/test_helpers/frame_codec.rs
src/client/builder.rs

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 6, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added built-in extractors for deserialising message payloads, handling streaming request bodies, and accessing connection metadata.
  • Breaking Changes

    • API method names standardised to American English spelling.
    • Payload accessor methods renamed to align with Rust conventions.
  • Bug Fixes

    • Enhanced deserialization failure tracking with per-connection limits.
    • Improved error handling during response operations.
  • Documentation

    • Updated migration guide documenting breaking changes and updated examples.
    • Added clarifications on protocol configuration requirements.

Walkthrough

This pull request consolidates internal refactoring, extends error handling, introduces new extractor types for message handling, refactors test infrastructure with fixtures and parameterised scenarios, and updates documentation to clarify API constraints and naming conventions across the codebase.

Changes

Cohort / File(s) Summary
API Naming and Breaking Changes
docs/v0-1-0-to-v0-2-0-migration-guide.md
Migration guide updated to document breaking API changes: normalised→normalized spelling, payload() method removed in favour of into_payload() for PacketParts and FragmentParts.
Builder Refactoring
src/app/builder.rs, src/app/builder_defaults.rs, src/app/builder_lifecycle.rs
Introduced DEFAULT_READ_TIMEOUT_MS constant, added rebuild_with_connection_type helper method to reconstruct WireframeApp with different connection types, removed Default trait bound from serializer in impl blocks, and refactored on_connection_setup to use centralised rebuild mechanism.
Error Handling and Response Context
src/app/frame_handling.rs
Added DeserFailureTracker for per-connection failure counting, introduced ResponseContext<'a, S, W, F> struct for carrying serialiser and framing state, improved error logging with structured parameters, and refactored tests with combined codec harness.
Codec and Recovery Configuration
src/codec/recovery.rs, src/codec/recovery/tests.rs
Added PartialEq and Eq derives to RecoveryConfig, renamed unused parameter in RecoveryPolicyHook, and introduced comprehensive test coverage for recovery policies, hooks, and configuration scenarios.
Client and Server Configuration
src/client/builder.rs, src/server/config/mod.rs, src/server/config/tests.rs
Centralised read buffer capacity constant in client builder, extracted default_worker_count helper in server config, and refactored server tests with connected_streams fixture, assert_local_addr_matches_listener helper, and parameterised BackoffScenario test suite.
Message Extractors Module
src/extractor/extractors.rs, src/extractor/mod.rs
Created new extractors module with Message, StreamingBody, and ConnectionInfo extractors implementing FromMessageRequest. Refactored mod.rs to use thiserror-based error handling with descriptive error variants and module-level re-exports.
Documentation and Metadata
src/message.rs, src/test_helpers/frame_codec.rs, src/app/builder_protocol.rs, src/fragment/tests.rs, src/hooks.rs
Updated documentation spelling to American English conventions, clarified ProtocolError constraint in builder API, enhanced doc comments for protocol and fragment testing scope, and added contextual notes on future relaxation of type constraints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

📦 Extractors bloom, builders grow lean,
With fragments restored and errors foreseen,
ProtocolError locked, timeouts aligned,
Recovery tested, and fixtures refined—
A tapestry stitched with precision and care. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the primary changes: refactoring errors, buffer sizing, tests, documentation, and adding fixtures—all central to this changeset.
Description check ✅ Passed The description comprehensively covers all major changes across multiple modules (core framework, client builder, codec recovery, extractors, server tests) and clearly relates to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 rename-payload-to-into-payload-q2jpyh

No actionable comments were generated in the recent review. 🎉


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

Reuse a shared helper when changing connection state types.
This keeps lifecycle builder reconstruction aligned with the core
builder and removes the ad hoc OnceCell instantiation.
…e scenarios

Expanded and diversified the backoff configuration tests for WireframeServer to cover multiple edge cases and delay configurations. Added parameterized tests using rstest, verifying initial and max delay clamping, swapping, and defaults for better test coverage and robustness.

Also:
- Cleaned up unused test utility functions and redundant assertions.
- Enhanced diagnostics in test assertions with scenario descriptions.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Ensure rstest fixtures avoid clippy lint issues by adding\nexplicit harness assertions and applying rustfmt-friendly layout\nfor test setup helpers.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Rebase onto origin main with conflict resolution Refactor error handling, buffer sizing, tests, and docs Feb 8, 2026
Adjust response logging and public doc comments in frame handling,\nand reuse a shared framed harness with client/server endpoints\nfor response tests. Also normalize message docs to use\nOxford -ize spelling.
Combine send_response_payload cases with a shared harness that\nincludes both client and server framed endpoints, reducing\nduplicate setup while preserving payload assertions.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos marked this pull request as ready for review February 9, 2026 11:34
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 9, 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:

Code Duplication

src/server/config/tests.rs:

What lead to degradation?

The module contains 2 functions with similar structure: test_bind_success,test_local_addr_after_bind

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

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 2 issues, and left some high level feedback:

  • The default_hook, context, and io_error fixtures in codec/recovery.rs contain let () = (); statements that are no-ops and can be removed to reduce noise without changing behavior.
  • In frame_handling.rs tests, build_harness constructs both server and client CombinedCodec instances from the same TestCodec value; if TestCodec’s internal state is meant to model per-endpoint behavior, consider cloning or constructing separate codec instances to avoid shared mutable state across the two sides.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `default_hook`, `context`, and `io_error` fixtures in `codec/recovery.rs` contain `let () = ();` statements that are no-ops and can be removed to reduce noise without changing behavior.
- In `frame_handling.rs` tests, `build_harness` constructs both server and client `CombinedCodec` instances from the same `TestCodec` value; if `TestCodec`’s internal state is meant to model per-endpoint behavior, consider cloning or constructing separate codec instances to avoid shared mutable state across the two sides.

## Individual Comments

### Comment 1
<location> `src/codec/recovery.rs:338` </location>
<code_context>
+        }
+    }
+
+    #[fixture]
+    fn default_harness() -> FramedHarness {
+        let harness = build_harness(64);
</code_context>

<issue_to_address>
**issue (complexity):** Consider inlining or replacing the trivial `rstest` fixtures with direct construction or simple helpers to reduce indirection in these tests.

The new `rstest` fixtures do add indirection without pulling their weight. You can keep the new behavior and `rstest` usage while simplifying the tests and removing the no-op abstraction.

### 1. Remove trivial fixtures and construct values inline

The fixtures:

```rust
#[fixture]
fn default_hook() -> DefaultRecoveryPolicy {
    let () = ();
    DefaultRecoveryPolicy
}

#[fixture]
fn context() -> CodecErrorContext {
    let () = ();
    CodecErrorContext::new()
}

#[fixture]
fn io_error() -> CodecError {
    let () = ();
    CodecError::Io(io::Error::other("test"))
}
```

are effectively just `Default::default()`/`new`/direct constructors, used only once each. You can inline them in the tests while still using `rstest`:

```rust
#[rstest]
fn default_recovery_policy_delegates_to_error() {
    use super::super::error::{EofError, FramingError};

    let default_hook = DefaultRecoveryPolicy;
    let context = CodecErrorContext::new();

    let err = CodecError::Framing(FramingError::OversizedFrame { size: 100, max: 50 });
    assert_eq!(
        default_hook.recovery_policy(&err, &context),
        RecoveryPolicy::Drop
    );

    let err = CodecError::Io(io::Error::other("test"));
    assert_eq!(
        default_hook.recovery_policy(&err, &context),
        RecoveryPolicy::Disconnect
    );

    let err = CodecError::Eof(EofError::CleanClose);
    assert_eq!(
        default_hook.recovery_policy(&err, &context),
        RecoveryPolicy::Disconnect
    );
}

#[rstest]
fn default_quarantine_duration_is_30_seconds() {
    let default_hook = DefaultRecoveryPolicy;
    let context = CodecErrorContext::new();
    let io_error = CodecError::Io(io::Error::other("test"));

    assert_eq!(
        default_hook.quarantine_duration(&io_error, &context),
        Duration::from_secs(30)
    );
}
```

This keeps:

- All current behavior and assertions.
- The `rstest` attribute (so you can later add parameterization if needed).

But removes:

- The three separate fixtures and their `let () = ();` no-ops.
- The cognitive overhead of chasing fixtures for very simple constructors.

### 2. If you do want shared construction logic, use plain helpers

If you expect these constructors to evolve and want a single point of change, simple helper functions are enough and avoid the fixture machinery:

```rust
fn make_default_hook() -> DefaultRecoveryPolicy {
    DefaultRecoveryPolicy
}

fn make_context() -> CodecErrorContext {
    CodecErrorContext::new()
}

fn make_io_error() -> CodecError {
    CodecError::Io(io::Error::other("test"))
}
```

Then in tests:

```rust
#[rstest]
fn default_quarantine_duration_is_30_seconds() {
    let default_hook = make_default_hook();
    let context = make_context();
    let io_error = make_io_error();

    assert_eq!(
        default_hook.quarantine_duration(&io_error, &context),
        Duration::from_secs(30)
    );
}
```

Either approach reduces complexity while preserving the new test behavior and keeping the door open for future `rstest` parameterization.
</issue_to_address>

### Comment 2
<location> `docs/v0-1-0-to-v0-2-0-migration-guide.md:3` </location>
<code_context>

-This guide summarizes the breaking changes required when migrating from
-wireframe v0.1.0 to v0.2.0.
+This guide summarizes the breaking changes you need to address when migrating
+from wireframe v0.1.0 to v0.2.0.

</code_context>

<issue_to_address>
**issue (review_instructions):** The new sentence uses the 2nd person pronoun "you", which the style guide forbids.

Consider rephrasing to avoid direct 2nd person, for example: "This guide summarizes the breaking changes that must be addressed when migrating from wireframe v0.1.0 to v0.2.0."

<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/codec/recovery.rs Outdated
Comment thread docs/v0-1-0-to-v0-2-0-migration-guide.md Outdated
@coderabbitai coderabbitai Bot added the codex label Feb 9, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2842e57ebc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/frame_handling.rs
Comment thread src/extractor.rs 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: 3

🤖 Fix all issues with AI agents
In `@docs/v0-1-0-to-v0-2-0-migration-guide.md`:
- Around line 3-4: The phrase "breaking changes you need to address when
migrating" uses a second-person pronoun; change it to a neutral, impersonal
phrasing by replacing "you need to address" with something like "to address" or
"that must be addressed" (i.e., update the sentence "This guide summarizes the
breaking changes you need to address when migrating from wireframe v0.1.0 to
v0.2.0." to "This guide summarizes the breaking changes to address when
migrating from wireframe v0.1.0 to v0.2.0." or "This guide summarizes breaking
changes that must be addressed when migrating from wireframe v0.1.0 to
v0.2.0.").

In `@src/app/frame_handling.rs`:
- Around line 255-260: Change the fixture to return a Result instead of
panicking: update fn default_harness() -> FramedHarness to something like fn
default_harness() -> Result<FramedHarness, Box<dyn std::error::Error>> (or your
crate's error type), call let harness = build_harness(64); then validate with if
harness.codec.max_frame_length() != 64 { return Err(format!("unexpected
max_frame_length: {}", harness.codec.max_frame_length()).into()); } and finally
Ok(harness); also update tests that consume default_harness to return Result and
call the fixture with ? so failures propagate rather than panic; reference
symbols: default_harness, FramedHarness, build_harness,
codec.max_frame_length().

In `@src/codec/recovery.rs`:
- Around line 338-355: The three fixture functions default_hook, context, and
io_error contain redundant no-op unit bindings (`let () = ();`) — remove those
lines from each fixture so the functions simply return DefaultRecoveryPolicy,
CodecErrorContext::new(), and CodecError::Io(io::Error::other("test"))
respectively; update only the bodies of the functions (default_hook, context,
io_error) to drop the `let () = ();` statements.

Comment thread docs/v0-1-0-to-v0-2-0-migration-guide.md Outdated
Comment thread src/app/frame_handling.rs
Comment thread src/codec/recovery.rs Outdated
…fig tests

- Remove unused rstest fixtures in codec recovery tests and initialize variables directly within test functions
- Add helper assert_local_addr_matches_listener function in server config tests to reduce duplication
- Refactor server config tests to use the new helper for clearer, more concise assertions

Additionally:
- Fix client codec initialization in frame_handling tests for clarity
- Minor wording improvement in migration guide docs

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Refactor error handling, buffer sizing, tests, and docs Refactor errors, buffer sizing, tests, and docs; add fixtures Feb 9, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Feb 9, 2026

@coderabbitai Have the following now been resolved?

Please address the comments from this code review:

## Overall Comments
- The `default_hook`, `context`, and `io_error` fixtures in `codec/recovery.rs` contain `let () = ();` statements that are no-ops and can be removed to reduce noise without changing behavior.
- In `frame_handling.rs` tests, `build_harness` constructs both server and client `CombinedCodec` instances from the same `TestCodec` value; if `TestCodec`’s internal state is meant to model per-endpoint behavior, consider cloning or constructing separate codec instances to avoid shared mutable state across the two sides.

## Individual Comments

### Comment 1
<location> `src/codec/recovery.rs:338` </location>
<code_context>
+        }
+    }
+
+    #[fixture]
+    fn default_harness() -> FramedHarness {
+        let harness = build_harness(64);
</code_context>

<issue_to_address>
**issue (complexity):** Consider inlining or replacing the trivial `rstest` fixtures with direct construction or simple helpers to reduce indirection in these tests.

The new `rstest` fixtures do add indirection without pulling their weight. You can keep the new behavior and `rstest` usage while simplifying the tests and removing the no-op abstraction.

### 1. Remove trivial fixtures and construct values inline

The fixtures:

```rust
#[fixture]
fn default_hook() -> DefaultRecoveryPolicy {
    let () = ();
    DefaultRecoveryPolicy
}

#[fixture]
fn context() -> CodecErrorContext {
    let () = ();
    CodecErrorContext::new()
}

#[fixture]
fn io_error() -> CodecError {
    let () = ();
    CodecError::Io(io::Error::other("test"))
}
```

are effectively just `Default::default()`/`new`/direct constructors, used only once each. You can inline them in the tests while still using `rstest`:

```rust
#[rstest]
fn default_recovery_policy_delegates_to_error() {
    use super::super::error::{EofError, FramingError};

    let default_hook = DefaultRecoveryPolicy;
    let context = CodecErrorContext::new();

    let err = CodecError::Framing(FramingError::OversizedFrame { size: 100, max: 50 });
    assert_eq!(
        default_hook.recovery_policy(&err, &context),
        RecoveryPolicy::Drop
    );

    let err = CodecError::Io(io::Error::other("test"));
    assert_eq!(
        default_hook.recovery_policy(&err, &context),
        RecoveryPolicy::Disconnect
    );

    let err = CodecError::Eof(EofError::CleanClose);
    assert_eq!(
        default_hook.recovery_policy(&err, &context),
        RecoveryPolicy::Disconnect
    );
}

#[rstest]
fn default_quarantine_duration_is_30_seconds() {
    let default_hook = DefaultRecoveryPolicy;
    let context = CodecErrorContext::new();
    let io_error = CodecError::Io(io::Error::other("test"));

    assert_eq!(
        default_hook.quarantine_duration(&io_error, &context),
        Duration::from_secs(30)
    );
}
```

This keeps:

- All current behavior and assertions.
- The `rstest` attribute (so you can later add parameterization if needed).

But removes:

- The three separate fixtures and their `let () = ();` no-ops.
- The cognitive overhead of chasing fixtures for very simple constructors.

### 2. If you do want shared construction logic, use plain helpers

If you expect these constructors to evolve and want a single point of change, simple helper functions are enough and avoid the fixture machinery:

```rust
fn make_default_hook() -> DefaultRecoveryPolicy {
    DefaultRecoveryPolicy
}

fn make_context() -> CodecErrorContext {
    CodecErrorContext::new()
}

fn make_io_error() -> CodecError {
    CodecError::Io(io::Error::other("test"))
}
```

Then in tests:

```rust
#[rstest]
fn default_quarantine_duration_is_30_seconds() {
    let default_hook = make_default_hook();
    let context = make_context();
    let io_error = make_io_error();

    assert_eq!(
        default_hook.quarantine_duration(&io_error, &context),
        Duration::from_secs(30)
    );
}
```

Either approach reduces complexity while preserving the new test behavior and keeping the door open for future `rstest` parameterization.
</issue_to_address>

### Comment 2
<location> `docs/v0-1-0-to-v0-2-0-migration-guide.md:3` </location>
<code_context>

-This guide summarizes the breaking changes required when migrating from
-wireframe v0.1.0 to v0.2.0.
+This guide summarizes the breaking changes you need to address when migrating
+from wireframe v0.1.0 to v0.2.0.

</code_context>

<issue_to_address>
**issue (review_instructions):** The new sentence uses the 2nd person pronoun "you", which the style guide forbids.

Consider rephrasing to avoid direct 2nd person, for example: "This guide summarizes the breaking changes that must be addressed when migrating from wireframe v0.1.0 to v0.2.0."

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

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

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

</details>
</issue_to_address>

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.

Caution

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

⚠️ Outside diff range comments (2)
src/codec/recovery.rs (2)

330-420: 🛠️ Refactor suggestion | 🟠 Major

Split this module to keep it under 400 lines.

Move the test module into a dedicated file to bring the module below the 400-line limit.

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


334-398: 🧹 Nitpick | 🔵 Trivial

Refactor tests to use rstest fixtures for shared setup.

Both default_recovery_policy_delegates_to_error and default_quarantine_duration_is_30_seconds duplicate identical initialisation of DefaultRecoveryPolicy and CodecErrorContext. Introduce fixtures to centralise this setup:

Suggested refactor
-    use rstest::rstest;
+    use rstest::{fixture, rstest};
+
+    #[fixture]
+    fn default_hook() -> DefaultRecoveryPolicy {
+        DefaultRecoveryPolicy
+    }
+
+    #[fixture]
+    fn context() -> CodecErrorContext {
+        CodecErrorContext::new()
+    }
@@
-    #[rstest]
-    fn default_recovery_policy_delegates_to_error() {
+    #[test]
+    fn default_recovery_policy_delegates_to_error(
+        default_hook: DefaultRecoveryPolicy,
+        context: CodecErrorContext,
+    ) {
         use super::super::error::{EofError, FramingError};
-        let default_hook = DefaultRecoveryPolicy;
-        let context = CodecErrorContext::new();
@@
-    #[rstest]
-    fn default_quarantine_duration_is_30_seconds() {
-        let default_hook = DefaultRecoveryPolicy;
-        let context = CodecErrorContext::new();
+    #[test]
+    fn default_quarantine_duration_is_30_seconds(
+        default_hook: DefaultRecoveryPolicy,
+        context: CodecErrorContext,
+    ) {
         let io_error = CodecError::Io(io::Error::other("test"));

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 9, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current state of the codec recovery tests to see if fixtures have been addressed
echo "=== src/codec/recovery.rs test fixtures ==="
sed -n '/^#\[cfg(test)\]/,/^}$/p' src/codec/recovery.rs | tail -100

Length of output: 2817


🏁 Script executed:

#!/bin/bash
# Check the migration guide for pronoun usage
echo "=== docs/v0-1-0-to-v0-2-0-migration-guide.md first 10 lines ==="
head -10 docs/v0-1-0-to-v0-2-0-migration-guide.md

Length of output: 353


🏁 Script executed:

#!/bin/bash
# Check frame_handling.rs for build_harness implementation
echo "=== src/app/frame_handling.rs build_harness function ==="
rg -A 20 'fn build_harness' src/app/frame_handling.rs

Length of output: 882


🏁 Script executed:

#!/bin/bash
# Get the full test module from codec/recovery.rs to check all fixtures
echo "=== Full test module from src/codec/recovery.rs ==="
ast-grep --pattern $'#[cfg(test)]
mod tests {
  $$$
}'

Length of output: 51


@leynos: Yes, all the review comments have been resolved! Here's the status:

✅ All Issues Resolved

1. Trivial fixtures in codec/recovery.rs — RESOLVED

The default_hook, context, and io_error fixtures with let () = (); no-ops have been removed. Tests now use inline construction as recommended:

#[rstest]
fn default_recovery_policy_delegates_to_error() {
    let default_hook = DefaultRecoveryPolicy;
    let context = CodecErrorContext::new();
    // ...
}

2. Shared state in build_harness — RESOLVED

The build_harness function in frame_handling.rs now creates separate TestCodec instances for client and server, avoiding shared mutable state:

fn build_harness(max_frame_length: usize) -> FramedHarness {
    let codec = TestCodec::new(max_frame_length);
    let client_codec = TestCodec::new(max_frame_length);  // Separate instance
    // ...
}

3. Second person pronoun in migration guide — RESOLVED

The documentation now uses the suggested passive construction without "you":

"This guide summarizes the breaking changes that must be addressed when migrating from wireframe v0.1.0 to v0.2.0."

All feedback has been successfully incorporated! 🎉

leynos and others added 2 commits February 9, 2026 12:00
* docs(protocol): add docs clarifying that ProtocolError must be ()

Added detailed comments to `WireframeApp::with_protocol` and the `WireframeProtocol` trait explaining that currently `ProtocolError` must be `()`. This ensures a uniform interface for dynamic dispatch and prevents leaking application-specific errors. Also added unit test module documentation for fragmentation and reassembly.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* docs(hooks): simplify example by removing unused error variable

In the WireframeProtocol trait example, changed the handle_error function to remove the unused `error` parameter and updated the logging statement accordingly to avoid unused variable warnings and improve clarity.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

---------

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Restore non-fatal handling for response fragmentation and codec encoding
errors in frame handling so handler payload mistakes do not disconnect
healthy client connections.

Keep transport failures fatal by propagating non-encoding send errors.
Also restore public visibility for MessageRequest.app_data to avoid a
breaking API change and remove the panicking rstest fixture pattern in
frame-handling tests.
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: 1

Caution

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

⚠️ Outside diff range comments (2)
src/extractor.rs (1)

1-6: ⚠️ Potential issue | 🟠 Major

Decompose this module—file exceeds 400 lines (currently 507).

Split the extractors into a submodule structure. A sensible decomposition:

src/extractor/
├── mod.rs          // re-exports, MessageRequest, Payload, FromMessageRequest trait, ExtractError
├── shared_state.rs // SharedState extractor
├── message.rs      // Message<T> extractor
├── streaming.rs    // StreamingBody extractor
└── connection.rs   // ConnectionInfo extractor

This keeps related types collocated whilst satisfying the 400-line limit. As per coding guidelines: "Files must not exceed 400 lines in length" and "Large modules must be decomposed".

src/app/frame_handling.rs (1)

237-239: ⚠️ Potential issue | 🟡 Minor

Add a module-level doc comment inside mod tests.

Insert a //! comment at Line 237 so the inner module meets the module-doc requirement.

Suggested fix
 #[cfg(test)]
 mod tests {
+    //! Tests for frame handling helpers and response sending.
     use bytes::Bytes;
     use futures::StreamExt;
     use rstest::rstest;
As per coding guidelines: "Every Rust module must begin with a module level (`//!`) comment explaining the module's purpose and utility."
🤖 Fix all issues with AI agents
In `@src/app/frame_handling.rs`:
- Around line 250-277: Tests duplicate harness setup; convert build_harness into
an rstest fixture and inject it into tests. Add use rstest::{fixture, rstest};
create a #[fixture] fn harness() -> FramedHarness { build_harness(64) }; then
update send_response_payload_behaviour and response_context_holds_references
signatures to accept mut harness: FramedHarness (and keep their #[case] params)
and remove the local let mut harness = build_harness(64) calls; keep
FramedHarness, build_harness, send_response_payload_behaviour, and
response_context_holds_references names unchanged.

Comment thread src/app/frame_handling.rs Outdated
Move recovery tests out of src/codec/recovery.rs into
src/codec/recovery/tests.rs so the production module stays under
the 400-line file-size limit.

Add rstest fixtures for DefaultRecoveryPolicy and CodecErrorContext
and reuse them in tests that previously duplicated setup.
Decompose extractor into a submodule layout by moving concrete
extractor implementations into src/extractor/extractors.rs and
converting extractor to directory module layout.

This keeps src/extractor/mod.rs below the 400-line threshold while
preserving the public extractor API via re-exports.

Update frame-handling tests to add the required module-level //! docs,
introduce an rstest harness fixture, and inject it into both tests to
remove duplicated setup.
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 Passed
6 Quality Gates Passed

See analysis details in CodeScene

Absence of Expected Change Pattern

  • wireframe/src/message.rs is usually changed with: wireframe/src/preamble.rs

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.

@leynos leynos merged commit d289ce8 into main Feb 9, 2026
6 checks passed
@leynos leynos deleted the rename-payload-to-into-payload-q2jpyh branch February 9, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant