Skip to content

Client streaming: ResponseStream API for multi-packet responses#462

Merged
leynos merged 8 commits intomainfrom
client-response-stream-multipacket-h12vcv
Feb 19, 2026
Merged

Client streaming: ResponseStream API for multi-packet responses#462
leynos merged 8 commits intomainfrom
client-response-stream-multipacket-h12vcv

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Feb 15, 2026

Summary

  • Plan and implement client-side streaming consumption for multi-packet responses (Response::Stream and Response::MultiPacket) via a typed ResponseStream API, with terminator detection, correlation validation, and back-pressure handling. This PR completes the client streaming surface, tests, and documentation for end-to-end streaming support.

Changes

API surface

  • Extend Packet with is_stream_terminator(&self) -> bool { false } (default) in src/app/envelope.rs
  • Client errors
    • Add StreamCorrelationMismatch variant to src/client/error.rs
  • Streaming primitives
    • New ResponseStream type: src/client/response_stream.rs
    • New streaming API: call_streaming and receive_streaming on WireframeClient (src/client/streaming.rs)
    • Re-export ResponseStream from public API (src/client/mod.rs)

Wiring and module structure

  • WireframeClient now wires in response_stream and streaming modules (src/client/mod.rs)

Implementation details

  • ResponseStream implements futures::Stream<Item = Result<P, ClientError>>
  • It borrows &mut WireframeClient to read framed data, deserialize via the client serializer, validate correlation_id on every frame, and detect terminator via is_stream_terminator. Terminator frames are swallowed and do not yield to the consumer.

Terminator behavior

  • Terminator frames are swallowed; the stream ends when a terminator is encountered. Data frames are yielded until the terminator, at which point the stream completes.

Testing

  • Unit tests: new src/client/tests/streaming.rs (8 unit tests covering data frames, terminator, correlation mismatch, empty and disconnect scenarios, end-to-end call_streaming, CID generation, and pre-sent envelope support)
  • BDD scaffolding
    • tests/features/client_streaming.feature (new)
    • tests/fixtures/client_streaming.rs (new)
    • tests/scenarios/client_streaming_scenarios.rs (new)
    • tests/steps/client_streaming_steps.rs (new)
    • Updated: tests/fixtures/mod.rs, tests/steps/mod.rs, tests/scenarios/mod.rs to include new modules

Documentation

  • Design docs: add section 12 to docs/multi-packet-and-streaming-responses-design.md describing client-side streaming consumption, terminator detection, ResponseStream, and API surface
  • User guide: docs/users-guide.md adds a Consuming streaming responses on the client section with Terminator detection, call_streaming and receive_streaming usage, and back-pressure notes
  • Roadmap: docs/roadmap.md marks 10.3.1 as done

Rationale / Design notes

  • Terminator detection is implemented on the client side via Packet::is_stream_terminator to mirror the server’s stream_end_frame hook. This keeps a single abstraction (Packet) governing frame semantics with a safe default.
  • ResponseStream is a concrete type (not a boxed trait object) to avoid dynamic dispatch overhead and to ensure exclusive access to the transport while streaming.
  • The stream borrows the client mutably (&mut WireframeClient) rather than splitting the Framed transport, simplifying borrow semantics and aligning with existing call/receive patterns.
  • Terminator frames are not yielded to the consumer; the stream ends when the terminator is encountered.
  • No external dependencies were added; existing crates (futures, tokio, tokio-util, pin_project_lite) are used.

Testing plan

  • Unit tests: 8 unit tests in src/client/tests/streaming.rs covering data ordering, terminator handling, correlation mismatches, empty streams, disconnects, CID generation, and pre-sent envelope usage.
  • BDD tests scaffolded under tests/features/client_streaming.feature with accompanying fixtures, steps, and scenarios to exercise client streaming behaviour end-to-end.
  • Validation gates: make check-fmt, make lint, and make test must pass.

How to test locally

  • Unit tests: cargo test -p wireframe --tests
  • Run full checks: make check-fmt && make lint && make test
  • Optional: run BDD tests if your environment is wired for rstest-bdd (tests/features and steps)

Compatibility and migration

  • Additive changes only; existing APIs remain backward-compatible.
  • New public API: ResponseStream and streaming helpers; no breaking changes to existing call/receive flow.
  • New tests and docs help validate the behavior without impacting existing consumers.

◳ Generated by DevBoxer ◰

📎 Task: https://www.devboxer.com/task/a01b9779-887b-49af-956b-8a345fe84d0e

…call_streaming

- Extend Packet trait with is_stream_terminator method for end-of-stream detection.
- Introduce ResponseStream type implementing futures::Stream to consume multi-frame server responses.
- Add call_streaming and receive_streaming methods on WireframeClient for high- and low-level streaming APIs.
- Implement correlation ID validation and terminator frame handling in streamed responses.
- Provide unit and BDD tests covering normal, empty, mismatch, and disconnect scenarios.
- Update docs: multi-packet design, user guide with streaming client usage, and roadmap.

This completes design goal G5 by enabling clients to asynchronously consume streaming and multi-packet responses with proper correlation and terminator semantics, propagating back-pressure naturally through TCP.

No breaking API changes; all additions are backward-compatible.

Closes #10.3.1.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Feb 15, 2026

Reviewer's Guide

Implements client-side streaming support for multi-packet server responses by introducing a typed ResponseStream that borrows WireframeClient, extends Packet with terminator detection, adds streaming-specific ClientError variants, wires new call_streaming/receive_streaming APIs, and adds comprehensive tests and documentation.

Sequence diagram for client call_streaming and ResponseStream consumption

sequenceDiagram
    actor ClientApp
    participant WireframeClient
    participant ResponseStream
    participant Server

    ClientApp->>WireframeClient: call_streaming(request)
    Note over WireframeClient: Ensure request has correlation_id (cid)
    WireframeClient->>Server: send request frame (cid)
    WireframeClient-->>ClientApp: ResponseStream

    loop while stream not terminated
        ClientApp->>ResponseStream: next().await
        ResponseStream->>WireframeClient: poll_next
        WireframeClient->>Server: read next frame
        Server-->>WireframeClient: data frame (cid)
        WireframeClient-->>ResponseStream: raw bytes
        ResponseStream->>ResponseStream: deserialize to Packet
        ResponseStream->>ResponseStream: validate correlation_id
        alt cid mismatch
            ResponseStream-->>ClientApp: Err StreamCorrelationMismatch
            note over ClientApp,ResponseStream: break
        else cid ok
            alt is_stream_terminator
                ResponseStream->>ResponseStream: mark terminated
                ResponseStream-->>ClientApp: None
                note over ClientApp,ResponseStream: break
            else data frame
                ResponseStream-->>ClientApp: Ok(packet)
            end
        end
    end
Loading

Class diagram for Packet, ResponseStream, WireframeClient, and ClientError

classDiagram
    class Packet {
        <<trait>>
        +correlation_id() Option~u64~
        +set_correlation_id(cid Option~u64~) void
        +id() u32
        +into_parts() PacketParts
        +from_parts(parts PacketParts) Self
        +is_stream_terminator() bool
    }

    class Envelope {
        +id: u32
        +correlation_id: Option~u64~
        +payload: Vec~u8~
        +new(id u32, correlation_id Option~u64~, payload Vec~u8~) Envelope
        +id() u32
        +correlation_id() Option~u64~
        +set_correlation_id(cid Option~u64~) void
        +into_parts() PacketParts
        +from_parts(parts PacketParts) Envelope
    }

    Packet <|.. Envelope

    class ClientError {
        <<enum>>
        Wireframe
        Serialize
        PreambleEncode
        PreambleWrite
        PreambleRead
        PreambleTimeout
        CorrelationMismatch
        StreamCorrelationMismatch
        StreamTerminated
        +decode(err any) ClientError
        +disconnected() ClientError
    }

    class ResponseStream {
        <<generic<'a, P, S, T, C>>>
        -client: &'a mut WireframeClient~S, T, C~
        -correlation_id: u64
        -terminated: bool
        -_phantom: PhantomData~fn() -> P~
        +new(client &'a mut WireframeClient~S, T, C~, correlation_id u64) ResponseStream~'a, P, S, T, C~
        +correlation_id() u64
        +is_terminated() bool
        -process_frame(bytes &[u8]) Option~Result~P, ClientError~~
        +poll_next(cx &mut Context) Poll~Option~Result~P, ClientError~~~
    }

    class WireframeClient {
        <<generic<S, T, C>>>
        -framed: Framed~T, LengthDelimitedCodec~
        -serializer: S
        -codec_config: ClientCodecConfig
        -correlation_counter: AtomicU64
        +call_streaming~P: Packet~(request P) Result~ResponseStream~P, S, T, C~, ClientError~
        +receive_streaming~P: Packet~(correlation_id u64) ResponseStream~P, S, T, C~
        +send_envelope(envelope Envelope) Result~u64, ClientError~
        +receive_envelope() Result~Envelope, ClientError~
    }

    Packet <.. ResponseStream : P - Packet

    WireframeClient o--> ResponseStream : borrows_mutably
    ClientError <.. ResponseStream : uses
    WireframeClient --> ClientError : returns
Loading

Flow diagram for ResponseStream poll_next and process_frame

flowchart TD
    A_start[poll_next called] --> B_checkTerminated{terminated?}
    B_checkTerminated -- yes --> Z_returnNone[Return None]
    B_checkTerminated -- no --> C_pollFramed[Poll client.framed.next]

    C_pollFramed --> D_state{Result}

    D_state -- Pending --> E_pending[Return Poll Pending]

    D_state -- Ready_None --> F_connClosed[Set terminated = true]
    F_connClosed --> F1_error[Return Some Err ClientError_disconnected]

    D_state -- Ready_Err --> G_transportErr[Set terminated = true]
    G_transportErr --> G1_error[Return Some Err ClientError_from_transport]

    D_state -- Ready_Ok_bytes --> H_processFrame[process_frame bytes]

    H_processFrame --> I_deserialize{deserialize ok?}
    I_deserialize -- no --> J_decodeErr[Set terminated = true]
    J_decodeErr --> J1_error[Return Some Err ClientError_decode]

    I_deserialize -- yes --> K_checkCid{received_cid == Some correlation_id?}
    K_checkCid -- no --> L_cidMismatch[Set terminated = true]
    L_cidMismatch --> L1_error[Return Some Err StreamCorrelationMismatch]

    K_checkCid -- yes --> M_checkTerminator{packet.is_stream_terminator?}
    M_checkTerminator -- yes --> N_setTerminated[Set terminated = true]
    N_setTerminated --> O_returnNone[Return None]

    M_checkTerminator -- no --> P_yield[Return Some Ok packet]
Loading

File-Level Changes

Change Details Files
Extend the Packet trait and client error surface to support stream terminator detection and streaming-specific failures.
  • Add Packet::is_stream_terminator with a safe default implementation returning false so protocols can define end-of-stream frames.
  • Introduce ClientError::StreamCorrelationMismatch and ClientError::StreamTerminated to distinguish streaming correlation issues and post-termination reads.
src/app/envelope.rs
src/client/error.rs
Introduce ResponseStream as a concrete futures::Stream over typed packets, driven by WireframeClient, with correlation validation and terminator handling.
  • Implement ResponseStream<'a, P, S, T, C> that borrows &mut WireframeClient and implements futures::Stream<Item = Result<P, ClientError>>.
  • In poll_next, read from the underlying Framed transport, deserialize via the client's serializer, validate correlation_id per frame, treat is_stream_terminator() as end-of-stream without yielding the terminator, and surface transport/decode errors via ClientError.
  • Expose correlation_id() and is_terminated() helpers for observability and tests.
src/client/response_stream.rs
Add high-level and low-level client streaming APIs and wire them into the public client module.
  • Add call_streaming to send a request (auto-generating correlation IDs when needed) and return a ResponseStream for its response.
  • Add receive_streaming to construct a ResponseStream for a correlation ID associated with a pre-sent request (e.g., via send_envelope).
  • Wire new streaming and response_stream modules into src/client/mod.rs and re-export ResponseStream from the crate's public API.
src/client/streaming.rs
src/client/mod.rs
Provide unit and BDD tests that exercise streaming behaviour, including ordering, terminator handling, correlation mismatches, empty streams, disconnects, and pre-sent envelopes.
  • Add src/client/tests/streaming.rs with a test Packet implementation that treats id==0 as a terminator and eight async tests covering data ordering, termination semantics, correlation mismatch, empty streams, disconnect behaviour, call_streaming CID generation, and receive_streaming with send_envelope.
  • Introduce a BDD fixture ClientStreamingWorld plus steps and scenarios for end-to-end client streaming behaviour, updating fixtures/scenarios/steps module registries accordingly.
  • Add tests/features/client_streaming.feature to define high-level streaming scenarios for multi-frame, empty, mismatch, and disconnect cases.
src/client/tests/streaming.rs
tests/fixtures/client_streaming.rs
tests/steps/client_streaming_steps.rs
tests/scenarios/client_streaming_scenarios.rs
tests/features/client_streaming.feature
tests/fixtures/mod.rs
tests/steps/mod.rs
tests/scenarios/mod.rs
Update design documentation, user guide, roadmap, and execution plan to document and track the new client streaming capabilities.
  • Extend the multi-packet and streaming design doc with a new section on client-side streaming consumption, including Packet::is_stream_terminator, ResponseStream behaviour, API surface, error handling, and back-pressure.
  • Update the users guide with a client streaming section showing how to implement is_stream_terminator, and how to use call_streaming/receive_streaming, including example code.
  • Mark roadmap item 10.3.1 as complete and add an execplan documenting constraints, decisions, risks, and validation for implementing client-side Response::Stream and Response::MultiPacket support.
docs/multi-packet-and-streaming-responses-design.md
docs/users-guide.md
docs/roadmap.md
docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md

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 15, 2026

Walkthrough

Implement client-side streaming response consumption via a new ResponseStream type; add Packet::is_stream_terminator(); introduce ClientError::StreamCorrelationMismatch; add WireframeClient::call_streaming() and receive_streaming(); include documentation, unit tests and BDD test coverage.

Changes

Cohort / File(s) Summary
Documentation & Planning
docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md, docs/multi-packet-and-streaming-responses-design.md, docs/roadmap.md, docs/users-guide.md
Add ExecPlan and design docs for client streaming; document call_streaming/receive_streaming, terminator detection and examples; mark 10.3.1 complete.
Packet Protocol Extension
src/app/envelope.rs
Add default trait method fn is_stream_terminator(&self) -> bool to Packet (default false) for end-of-stream detection.
Client Error Handling
src/client/error.rs
Add ClientError::StreamCorrelationMismatch { expected: Option<u64>, received: Option<u64> } with message and docs.
Streaming Core Implementation
src/client/response_stream.rs, src/client/streaming.rs
Add ResponseStream<'a, P, S, T, C> implementing futures::Stream with correlation validation, decode error handling and terminator detection; add WireframeClient::call_streaming() (async) and receive_streaming().
Client Module Surface
src/client/mod.rs
Add response_stream and streaming modules; re-export ResponseStream.
Client Unit Tests & Infra
src/client/tests/mod.rs, src/client/tests/streaming.rs, src/client/tests/streaming_infra.rs
Add unit tests and test-infrastructure: TestStreamEnvelope, TestServer, TestClient, Correlation/Message ID newtypes and helpers covering ordering, terminator, decode errors, correlation mismatches and disconnects.
BDD Features & Fixtures
tests/features/client_streaming.feature, tests/fixtures/client_streaming.rs, tests/fixtures/mod.rs, tests/fixtures/client_streaming/*.rs, tests/scenarios/client_streaming_scenarios.rs, tests/scenarios/mod.rs, tests/steps/client_streaming_steps.rs, tests/steps/mod.rs
Add Gherkin feature, fixtures and step definitions for multi-frame, empty, mismatch and disconnect scenarios; provide ClientStreamingWorld and helpers for end-to-end verification.

Sequence Diagram

sequenceDiagram
    participant Client as Client Code
    participant WFC as WireframeClient
    participant RS as ResponseStream
    participant Transport as Framed\<T>
    participant Server as Server

    Client->>WFC: call_streaming(request)
    WFC->>WFC: compute/assign correlation_id
    WFC->>WFC: serialise request
    WFC->>Transport: send bytes
    Transport->>Server: transmit
    WFC-->>Client: ResponseStream{correlation_id}

    loop Poll next frame
        Client->>RS: poll_next()
        RS->>Transport: poll (read frame bytes)
        Transport->>Server: await
        Server-->>Transport: frame bytes or EOF
        Transport-->>RS: Poll::Ready(bytes)

        alt Decode success
            RS->>RS: validate correlation_id
            alt ID matches
                RS->>RS: check packet.is_stream_terminator()
                alt Terminator detected
                    RS->>RS: set terminated = true
                    RS-->>Client: Poll::Ready(None)
                else Data frame
                    RS-->>Client: Poll::Ready(Some(Ok(packet)))
                end
            else ID mismatch
                RS->>RS: set terminated = true
                RS-->>Client: Poll::Ready(Some(Err(StreamCorrelationMismatch)))
            end
        else Decode failure
            RS->>RS: set terminated = true
            RS-->>Client: Poll::Ready(Some(Err(decode error)))
        end
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🌊 Frame by frame the messages stream,
Hold correlation, keep the dream,
Terminator found, the flow will cease,
ResponseStream yields packets, piece by piece. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title clearly and specifically describes the main feature—client-side streaming consumption via ResponseStream API for multi-packet responses—which is the primary focus of this comprehensive changeset across docs, client library, and tests.
Description check ✅ Passed PR description comprehensively covers changes, testing, documentation, and rationale for client-side streaming implementation and is directly relevant to the changeset.

✏️ 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 client-response-stream-multipacket-h12vcv

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

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

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

src/client/tests/streaming.rs

Comment on lines +424 to +450

async fn receive_streaming_works_with_pre_sent_request(correlation_id: u64) {
    let frames = vec![
        TestStreamEnvelope::data(1, correlation_id, vec![55]),
        TestStreamEnvelope::terminator(correlation_id),
    ];

    let server = spawn_streaming_server(frames).await;
    let mut client = WireframeClient::builder()
        .connect(server.addr)
        .await
        .expect("connect client");

    // Send the request manually via send_envelope.
    let request = TestStreamEnvelope::data(99, correlation_id, vec![]);
    let cid = client.send_envelope(request).await.expect("send");

    // Use receive_streaming to consume the response.
    let mut stream = client.receive_streaming::<TestStreamEnvelope>(cid);

    let frame = stream.next().await.expect("data frame").expect("Ok");
    assert_eq!(frame.payload, vec![55]);

    let end = stream.next().await;
    assert!(end.is_none(), "stream should terminate");

    server.handle.abort();
}

❌ New issue: Code Duplication
The module contains 4 functions with similar structure: call_streaming_sends_request_and_returns_stream,receive_streaming_works_with_pre_sent_request,spawn_disconnect_server,spawn_streaming_server

@leynos
Copy link
Copy Markdown
Owner Author

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

src/client/tests/streaming.rs

Comment on file

//! Unit tests for client streaming response APIs.

❌ New issue: Primitive Obsession
In this module, 50.0% of all function arguments are primitive types, threshold = 30.0%

@leynos
Copy link
Copy Markdown
Owner Author

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

tests/steps/client_streaming_steps.rs

Comment on lines +20 to +27

fn given_mismatch_server(client_streaming_world: &mut ClientStreamingWorld) -> TestResult {
    client_streaming_world.abort_server();
    let rt = tokio::runtime::Runtime::new()?;
    rt.block_on(async {
        client_streaming_world.start_mismatch_server().await?;
        client_streaming_world.connect_client().await
    })
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: given_disconnect_server,given_mismatch_server

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

… setup

- Extract test server spawning helper functions with detailed control
- Introduce TestServer struct managing server task lifecycle
- Replace multiple test server functions with unified approach supporting terminator control
- Simplify client connection setup in tests via helper functions
- Add flowchart diagram documenting ResponseStream poll_next control flow logic
- Refactor BDD step definitions to reuse server restart helper
- Clean up test code for clarity and reduce boilerplate

This refactor improves test reliability and readability, providing clearer
server lifecycle handling and more consistent test setup patterns.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Plan client streaming multi-packet support Implement client streaming multi-packet support Feb 15, 2026
@leynos leynos marked this pull request as ready for review February 15, 2026 15:36
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 5 issues, and left some high level feedback:

  • The ClientError::StreamTerminated variant is documented in the design/user docs as being returned when polling after stream end, but the current ResponseStream implementation never constructs it (and instead just returns None once terminated is set); consider either wiring this error into poll_next or updating/removing the variant and docs to keep behavior consistent.
  • The BDD step implementations in tests/steps/client_streaming_steps.rs create a new tokio::runtime::Runtime and block on it per step, which is relatively heavy and easy to misuse; consider using async test/step support or a shared runtime instead of per-step runtime construction and blocking.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `ClientError::StreamTerminated` variant is documented in the design/user docs as being returned when polling after stream end, but the current `ResponseStream` implementation never constructs it (and instead just returns `None` once `terminated` is set); consider either wiring this error into `poll_next` or updating/removing the variant and docs to keep behavior consistent.
- The BDD step implementations in `tests/steps/client_streaming_steps.rs` create a new `tokio::runtime::Runtime` and block on it per step, which is relatively heavy and easy to misuse; consider using async test/step support or a shared runtime instead of per-step runtime construction and blocking.

## Individual Comments

### Comment 1
<location> `src/app/envelope.rs:102-111` </location>
<code_context>
+    ///     fn set_correlation_id(&mut self, cid: Option<u64>) { self.correlation_id = cid; }
+    /// }
+    ///
+    /// impl Packet for MyFrame {
+    ///     fn id(&self) -> u32 { self.id }
+    ///     fn into_parts(self) -> PacketParts {
+    ///         PacketParts::new(self.id, self.correlation_id, self.payload)
+    ///     }
+    ///     fn from_parts(parts: PacketParts) -> Self {
+    ///         Self {
+    ///             id: parts.id(),
+    ///             correlation_id: parts.correlation_id(),
+    ///             payload: parts.into_payload(),
+    ///         }
+    ///     }
+    ///     fn is_stream_terminator(&self) -> bool { self.id == 0 }
+    /// }
+    ///
</code_context>

<issue_to_address>
**issue (bug_risk):** Doc example for `Packet` is unlikely to compile because `MyFrame` does not implement the `Message` supertrait.

Because `Packet` requires `Message` (and others), this impl will not compile unless `MyFrame` also implements `Message`. As this is a normal doc test, it will fail during docs build. Consider either adding a `Message` impl for `MyFrame` in the example or marking the example with `ignore`/`no_compile` if you don’t want to depend on the real `Message` implementation.
</issue_to_address>

### Comment 2
<location> `src/client/tests/streaming.rs:254-277` </location>
<code_context>
+
+#[rstest]
+#[tokio::test]
+async fn response_stream_validates_correlation_id(correlation_id: u64) {
+    let wrong_cid = correlation_id + 999;
+    let server = spawn_mismatch_server(wrong_cid).await;
+    let mut client = create_test_client(server.addr).await;
+
+    let mut request = TestStreamEnvelope::data(99, correlation_id, vec![]);
+    request.set_correlation_id(Some(correlation_id));
+
+    let mut stream = client
+        .call_streaming::<TestStreamEnvelope>(request)
+        .await
+        .expect("call_streaming");
+
+    let result = stream.next().await;
+    match result {
+        Some(Err(ClientError::StreamCorrelationMismatch { expected, received })) => {
+            assert_eq!(expected, Some(correlation_id));
+            assert_eq!(received, Some(wrong_cid));
+        }
+        other => panic!("expected StreamCorrelationMismatch, got {other:?}"),
+    }
+}
</code_context>

<issue_to_address>
**suggestion (testing):** Add an assertion that the stream is marked terminated and does not yield further items after a StreamCorrelationMismatch error.

This test only checks that the first item is a `StreamCorrelationMismatch`. Please extend it to also assert that, after the mismatch:

- `stream.is_terminated()` is `true`, and
- a subsequent `stream.next().await` returns `None` (or the appropriate terminal result).

This will verify the stream state machine and ensure we don’t keep reading or yielding frames after a correlation mismatch.

```suggestion
#[rstest]
#[tokio::test]
async fn response_stream_validates_correlation_id(correlation_id: u64) {
    let wrong_cid = correlation_id + 999;
    let server = spawn_mismatch_server(wrong_cid).await;
    let mut client = create_test_client(server.addr).await;

    let mut request = TestStreamEnvelope::data(99, correlation_id, vec![]);
    request.set_correlation_id(Some(correlation_id));

    let mut stream = client
        .call_streaming::<TestStreamEnvelope>(request)
        .await
        .expect("call_streaming");

    let result = stream.next().await;
    match result {
        Some(Err(ClientError::StreamCorrelationMismatch { expected, received })) => {
            assert_eq!(expected, Some(correlation_id));
            assert_eq!(received, Some(wrong_cid));
        }
        other => panic!("expected StreamCorrelationMismatch, got {other:?}"),
    }

    // After a correlation mismatch, the stream should be marked terminated
    // and yield no further items.
    assert!(
        stream.is_terminated(),
        "stream should be terminated after correlation mismatch"
    );

    let next = stream.next().await;
    assert!(
        next.is_none(),
        "no further items should be yielded after correlation mismatch"
    );
}
```
</issue_to_address>

### Comment 3
<location> `src/client/tests/streaming.rs:78-83` </location>
<code_context>
+    }
+}
+
+/// Serializes a `TestStreamEnvelope` to bytes for transmission.
+fn serialize_envelope(envelope: &TestStreamEnvelope) -> Bytes {
+    Bytes::from(
+        BincodeSerializer
+            .serialize(envelope)
+            .expect("serialize test envelope"),
+    )
+}
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test for how ResponseStream behaves when it receives malformed or undecodable frames.

Existing tests cover happy paths and some error cases, but not deserialization failure inside `process_frame` (where `serializer.deserialize` returns an error).

Please add a test that sends invalid bincode for `TestStreamEnvelope` (e.g., truncated or random bytes) from a test server, then asserts that:
- `stream.next().await` yields `Some(Err(ClientError::Decode(_)))` (or the appropriate variant), and
- the stream terminates afterward.

This will verify that decode failures are handled deterministically and stop further polling of the transport.

Suggested implementation:

```rust
/// Serializes a `TestStreamEnvelope` to bytes for transmission.
fn serialize_envelope(envelope: &TestStreamEnvelope) -> Bytes {
    Bytes::from(
        BincodeSerializer
            .serialize(envelope)
            .expect("serialize test envelope"),
    )
}

/// Ensures that `ResponseStream` surfaces decode errors and terminates after a
/// malformed frame is received.
#[tokio::test]
async fn response_stream_terminates_after_decode_error() {
    use futures::StreamExt;

    // Set up a test transport and response stream using the existing test
    // harness types used elsewhere in this file.
    //
    // We assume there is a constructor similar to `TestClientTransport::new`
    // that takes a sender/receiver of `Bytes` and that `ResponseStream` is
    // constructed in the same way as other tests in this module.
    let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<Bytes>();

    // Replace `TestClientTransport::new(rx)` and `ResponseStream::new(...)`
    // with the concrete helpers/constructors already used in this file.
    let transport = TestClientTransport::new(rx);
    let mut stream: ResponseStream<TestStreamEnvelope, BincodeSerializer> =
        ResponseStream::new(transport, BincodeSerializer);

    // Send invalid / undecodable bincode bytes for `TestStreamEnvelope`.
    //
    // We deliberately send something that is not a valid `TestStreamEnvelope`
    // encoding so that `serializer.deserialize` inside `process_frame`
    // returns an error.
    let invalid_bytes = Bytes::from_static(b"\x01\x02\x03\x04");
    tx.send(invalid_bytes).expect("send invalid frame");

    // First poll should yield a decode error.
    let first = stream.next().await;
    match first {
        Some(Err(ClientError::Decode(_))) => {}
        other => panic!("expected decode error from stream, got: {:?}", other),
    }

    // After a decode failure, the stream should terminate and not yield any
    // further items on subsequent polls.
    let second = stream.next().await;
    assert!(
        second.is_none(),
        "stream should terminate after decode error, got: {:?}",
        second
    );
}

```

The suggested test assumes the presence of several items that you likely already have in this test module:

1. A `TestClientTransport` type (or equivalent) that can be constructed from a `tokio::sync::mpsc::UnboundedReceiver<Bytes>` (or similar). Update `TestClientTransport::new(rx)` to match the actual constructor in your test harness.
2. A `ResponseStream<TestStreamEnvelope, BincodeSerializer>` type and a `ResponseStream::new(transport, BincodeSerializer)` constructor. Adjust the type parameters and constructor call to match your existing tests.
3. A `ClientError::Decode` variant. If the decode error is represented differently (e.g., `ClientError::Deserialization` or wrapped in another error type), update the `match` arm accordingly.
4. Ensure that the necessary imports are present at the top of the file:
   - `use bytes::Bytes;`
   - `use crate::client::error::ClientError;`
   - `use crate::client::streaming::ResponseStream;`
   - `use crate::client::tests::util::TestClientTransport;` (or wherever your test transport lives)
   - `use crate::client::tests::streaming::TestStreamEnvelope;` (if not already in scope)
   - `use crate::serialization::BincodeSerializer;`
   - `use futures::StreamExt;`
5. If your existing tests use a different async test attribute (e.g., `#[async_std::test]` or `#[tokio::test(flavor = "current_thread")]`), adjust the attribute on the new test accordingly.

Once these are wired to your actual types and constructors, this test will verify that a decode failure in `process_frame` produces a `ClientError::Decode` and that the `ResponseStream` terminates afterward.
</issue_to_address>

### Comment 4
<location> `docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md:48` </location>
<code_context>
+- All code must pass `make check-fmt`, `make lint`, and `make test`.
+- Documentation must use en-GB-oxendict spelling per `AGENTS.md`.
+- No single source file may exceed 400 lines per `AGENTS.md`.
+- BDD tests must use `rstest-bdd` 0.5.0 (not the removed Cucumber
+  framework).
+
</code_context>

<issue_to_address>
**issue (review_instructions):** The acronym “BDD” is introduced without being expanded on first use; it should be defined (for example, “behaviour-driven development (BDD)”).

To comply with the style guidance, expand the acronym the first time it appears in this document, for example:

"- Behaviour-driven development (BDD) tests must use `rstest-bdd` 0.5.0 (not the removed Cucumber framework)."

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

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

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

</details>
</issue_to_address>

### Comment 5
<location> `docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md:226` </location>
<code_context>
+
+### Back-pressure
+
+Back-pressure is natural via TCP flow control. If the client reads slowly from
+the socket, the TCP receive buffer fills, the server's TCP send buffer fills,
+the server's `write` suspends, and the server stops polling the response
</code_context>

<issue_to_address>
**issue (review_instructions):** The acronym “TCP” is used here without being expanded on first use in this document; it should be defined when first mentioned.

Consider expanding TCP the first time it appears, for example:

"Back-pressure is natural via Transmission Control Protocol (TCP) flow control. If the client reads slowly from …"

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

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

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

</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/envelope.rs
Comment thread src/client/tests/streaming.rs
Comment thread src/client/tests/streaming.rs Outdated
Comment thread docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md Outdated
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: 09215f7f36

ℹ️ 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/client/response_stream.rs
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: 8

🤖 Fix all issues with AI agents
In `@docs/multi-packet-and-streaming-responses-design.md`:
- Around line 641-643: The doc uses the term serializer as an API identifier but
doesn't mark the spelling explicitly; update the sentence "Deserializes it into
`P` using the client's configured serializer." to wrap the API term serializer
in inline code formatting (i.e., mark `serializer`) so the US spelling is
clearly shown as an identifier; ensure the same treatment is applied wherever
the API term serializer appears (e.g., in related sentences or bullets) to
conform to the coding guideline about US spelling for API identifiers.
- Around line 648-697: The HTML comment containing the accessible description
for the Mermaid diagram should be moved out so it is visible to screen readers;
create a short prose paragraph immediately before the ```mermaid``` block that
describes the control flow of ResponseStream::poll_next (mentioning terminated
check, polling client.framed.next, the four poll outcomes: Pending,
Ready(None)=disconnected, Ready(Err)=transport error, Ready(Ok
bytes)=process_frame and its subchecks including deserialize failure,
correlation id mismatch, stream terminator and yielding packet) so the
description is not inside an HTML comment and appears as normal text before the
diagram.

In `@docs/users-guide.md`:
- Around line 1317-1320: Replace the British spelling "recognise" with the
Oxford English spelling "recognize" in the documentation sentence describing
Packet::is_stream_terminator so the line reads that protocols override it to
"recognize their terminator format"; update any adjacent occurrences in the same
paragraph mentioning Packet::is_stream_terminator to maintain consistent
en-GB-oxendict -ize spelling.

In `@src/client/tests/streaming.rs`:
- Around line 1-5: This file exceeds the 400-line guideline because test
infrastructure is embedded in the streaming tests; move helpers like
TestStreamEnvelope and TestServer out of src/client/tests/streaming.rs into a
new sibling module or fixture file (e.g., src/client/tests/streaming_fixtures.rs
or a nested mod streaming::fixtures) and update streaming.rs to import them (use
crate::tests::streaming_fixtures::{TestStreamEnvelope, TestServer} or the
appropriate path); ensure visibility (pub or pub(crate)) and adjust any module
paths in tests so the tests compile and the main streaming.rs falls under 400
lines.
- Around line 78-169: Change the shared test helpers to return Result instead of
panicking: update serialize_envelope, spawn_test_server, spawn_mismatch_server,
and create_test_client to return Result<...> (appropriate concrete Result types
for their return values) and remove all .expect(...) calls, using the ? operator
to propagate errors from TcpListener::bind, listener.local_addr,
listener.accept, transport.send/next, BincodeSerializer.serialize, and
WireframeClient::builder().connect; adjust TestServer construction to return
Ok(TestServer { ... }) and update all test call sites to use ? (or .await?) so
errors bubble up rather than causing panics. Ensure function signatures and
return types (e.g., Result<TestServer, E> and Result<WireframeClient<...>, E>)
are updated consistently.
- Around line 20-34: Replace the primitive type aliases CorrelationId,
MessageId, and Payload with NewType tuple structs (either via newt-hype newtype!
or explicit tuple structs) and update TestStreamEnvelope to use those new types
for fields (id: MessageId, correlation_id: Option<CorrelationId>, payload:
Payload); change TERMINATOR_ID to the corresponding MessageId newtype value and
propagate the new types through all relevant functions and tests, specifically
updating signatures and usages in serialize_envelope, spawn_test_server,
spawn_mismatch_server, and any test fixtures or comparisons to construct and
pattern-match the new tuple structs instead of raw u32/u64/Vec<u8>.

In `@tests/fixtures/client_streaming.rs`:
- Around line 77-87: The function serialize_to_bytes currently panics via
.expect() and uses an allow for clippy::expect_used; remove that suppression and
change the signature of serialize_to_bytes to return Result<Bytes, E> (e.g.,
Result<Bytes, bincode::Error> or impl From<BincodeError>) instead of Bytes, and
replace the .expect("serialize test frame") with returning the serialization
error (i.e., map the BincodeSerializer.serialize(self) result into Bytes on Ok
and propagate Err). Update callers of serialize_to_bytes to propagate or handle
the Result (they already handle Results in tests), and remove the test-only
rationale comment.
- Around line 322-333: Remove the clippy suppression and replace the unchecked
casts in send_data_frames: change the usages of i as u32 and i as u8 when
building StreamTestEnvelope::data to fallible conversions
(u32::try_from(i).expect("count is bounded by test data") and
u8::try_from(i).expect("count is bounded by test data")), and remove the
#[expect(clippy::cast_possible_truncation,...)] attribute; apply the same
replacement in the verify_frame_order helper where i is cast to u32/u8 so all
potential truncations use try_from with the same explanatory expect message.

Comment thread docs/multi-packet-and-streaming-responses-design.md Outdated
Comment thread docs/multi-packet-and-streaming-responses-design.md Outdated
Comment thread docs/users-guide.md
Comment thread src/client/tests/streaming.rs
Comment thread src/client/tests/streaming.rs Outdated
Comment thread src/client/tests/streaming.rs Outdated
Comment thread tests/fixtures/client_streaming.rs Outdated
Comment thread tests/fixtures/client_streaming.rs Outdated
…e tests

- Extract shared test infrastructure into src/client/tests/streaming_infra.rs to reduce duplication and improve code organization.
- Introduce newtypes (CorrelationId, MessageId, Payload) for clearer test semantics.
- Replace ad-hoc serialization helpers with fallible versions.
- Add detailed async test functions with improved error handling and results.
- Cover more edge cases: decode errors, correlation ID mismatches, connection closes.
- Update existing tests for better clarity and correctness with explicit correlation IDs and consistent payload construction.
- Minor documentation and style improvements in test-related code.

This improves maintainability and robustness of client streaming functionality tests.

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

This comment was marked as outdated.

@leynos leynos changed the title Implement client streaming multi-packet support Client streaming multi-packet support implemented Feb 18, 2026
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: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md`:
- Around line 15-22: This section must explicitly state that the multi-packet
API is tuple-based and call out the helper constructors that prepare the
response channel pair: add a short note after the paragraph describing
Response::Stream and Response::MultiPacket explaining that multi-packet
responses are represented as a tuple-based API and mention the helper
constructors Response::with_channel and Response::with_channel_and_initial
(which create the channel pair and optionally include an initial frame). Ensure
the wording is brief, follows the existing style, and references those
constructor names exactly.
- Around line 24-29: Reflow the long paragraph and any other over-80-column
lines in this section so all Markdown paragraphs and bullets are wrapped at 80
columns; specifically rewrap the sentence describing Observable outcome (the
line containing client.call_streaming(request), ResponseStream<P>, Result<P,
ClientError>, and the list of tests like single-frame, multi-frame,
empty-stream, terminator validation, correlation mismatch, and back-pressure) so
no line exceeds 80 characters while preserving the original wording and
punctuation.
- Around line 48-49: The docs still mention the removed Cucumber framework;
update the sentence that currently reads "BDD tests must use `rstest-bdd` 0.5.0
(not the removed Cucumber framework)" to remove any reference to Cucumber and
state rstest-bdd-only wording (e.g., "BDD tests must use `rstest-bdd` 0.5.0").
Ensure other occurrences of "Cucumber" in this document are replaced or retired
so all examples and guides refer only to `rstest-bdd`.

In `@docs/multi-packet-and-streaming-responses-design.md`:
- Around line 648-659: The paragraph describing the decision tree for
ResponseStream::poll_next is too long and must be reflowed to 80-column width;
split the long sentence into multiple wrapped lines so each line is <=80 columns
while preserving the exact technical terms and order (ResponseStream::poll_next,
Framed, process_frame, is_stream_terminator, StreamCorrelationMismatch), keeping
the same logical flow (terminated check, Framed poll outcomes
Pending/Ready(None)/Ready(Err)/Ready(Ok(bytes)), process_frame deserialize and
is_stream_terminator checks, correlation ID validation) and maintaining
punctuation and code span formatting.
- Around line 720-727: The docs list ClientError::StreamTerminated as a
ResponseStream error but ResponseStream::poll_next currently returns None on
termination and never yields ClientError::StreamTerminated; update the docs to
remove or reword that row to state that terminated streams cause poll_next to
return None (no error), or alternatively implement ClientError::StreamTerminated
by changing ResponseStream::poll_next to return
Err(ClientError::StreamTerminated) when polling after termination — touch the
ResponseStream::poll_next implementation and the ClientError enum
(ClientError::StreamTerminated) accordingly depending on which behavior you
choose so code and docs match.

In `@docs/users-guide.md`:
- Around line 1309-1313: Reflow the new client streaming documentation so all
Markdown paragraphs and list items are wrapped at 80 columns: break long lines
in the paragraph that mentions Response::Stream, Response::MultiPacket and the
methods call_streaming/receive_streaming on WireframeClient, and wrap the
sentence describing the returned ResponseStream and its behavior into multiple
lines not exceeding 80 characters per line; preserve punctuation, code spans,
and list structure while ensuring no line exceeds the 80-column limit.

In `@src/app/envelope.rs`:
- Around line 83-115: The doctest for MyFrame uses the wrong bincode derive:
update the derives on the MyFrame struct so it implements Encode and
BorrowDecode (for<'de> BorrowDecode<'de, ()>) instead of Decode; locate the
MyFrame definition in the example and replace the bincode::Decode derive with
bincode::BorrowDecode so MyFrame satisfies the Message trait bounds used by
Packet/PacketParts.

In `@tests/fixtures/client_streaming.rs`:
- Around line 25-83: The StreamTestEnvelope currently uses primitives (u32, u64,
Vec<u8>) causing "integer soup"; replace those with NewType wrappers (e.g.,
MessageId, CorrelationId, Payload from newt-hype or custom tuple structs) and
update all related code: change the struct fields in StreamTestEnvelope, update
TERMINATOR_ID to a MessageId constant, adjust Packet impl methods (id,
into_parts, from_parts, is_stream_terminator) to work with the new types, and
update helper constructors StreamTestEnvelope::data and ::terminator and
serialize_to_bytes to accept/produce the NewTypes; ensure comparisons
(is_stream_terminator) and conversions to/from PacketParts use the appropriate
newtype accessors or conversions.

Comment thread docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md Outdated
Comment thread docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md Outdated
Comment thread docs/multi-packet-and-streaming-responses-design.md
Comment thread docs/multi-packet-and-streaming-responses-design.md Outdated
Comment thread docs/users-guide.md Outdated
Comment thread src/app/envelope.rs
Comment thread tests/fixtures/client_streaming.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

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

src/client/tests/streaming.rs

Comment on lines +334 to +358

async fn receive_streaming_works_with_pre_sent_request(
    correlation_id: CorrelationId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let cid = correlation_id;
    let frames = vec![
        TestStreamEnvelope::data(MessageId::new(1), cid, Payload::new(vec![55])),
        TestStreamEnvelope::terminator(cid),
    ];

    let (mut client, _server) = setup_streaming_test(frames).await?;

    // Send the request manually via send_envelope.
    let request = TestStreamEnvelope::data(MessageId::new(99), cid, Payload::new(vec![]));
    let sent_cid = client.send_envelope(request).await.expect("send");

    // Use receive_streaming to consume the response.
    let mut stream = client.receive_streaming::<TestStreamEnvelope>(sent_cid);

    let frame = stream.next().await.expect("data frame").expect("Ok");
    assert_eq!(frame.payload, vec![55]);

    let end = stream.next().await;
    assert!(end.is_none(), "stream should terminate");
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: call_streaming_sends_request_and_returns_stream,receive_streaming_works_with_pre_sent_request

@coderabbitai

This comment was marked as resolved.

@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

…d-stream termination

- Removed the StreamTerminated variant from ClientError.
- ResponseStream now returns None after termination or fatal error, following futures::Stream fused-stream idiom.
- Updated docs and tests to reflect the removal and use of None to indicate stream end.
- Refactored client streaming tests and fixtures to align with the fused-stream pattern.
- Simplified error handling and documentation accordingly.

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

This comment was marked as resolved.

…or handling

- Introduce strongly typed wrappers (CorrelationId, MessageId, Payload) for stream data elements.
- Update StreamTestEnvelope and related traits to use these types for better type safety.
- Handle tokio runtime creation errors gracefully in ClientStreamingWorld with optional error storage.
- Change test steps to propagate errors instead of panicking.
- Minor doc and error comment corrections related to streaming responses.
- Remove some redundant attributes and improve encoding derives.

This refactor enhances type safety for streaming data, improves error handling in tests,
and cleans up test fixtures to reduce panic risk and improve clarity.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Client streaming multi-packet support implemented Client streaming: ResponseStream API for multi-packet responses Feb 18, 2026
coderabbitai[bot]

This comment was marked as resolved.

…ssure in docs

Enhanced the users guide to explicitly explain that ResponseStream holds an exclusive mutable borrow on WireframeClient, preventing other I/O until the stream is dropped or consumed. Also clarified TCP back-pressure behavior in client streaming section.

Additional minor improvements:
- Reformatted error display annotation for StreamCorrelationMismatch.
- Added bincode::BorrowDecode derive macros for test fixture types.
- Minor tidying in client_streaming tests, including server abort before start and error verification refinement.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@coderabbitai coderabbitai Bot added the codex label Feb 18, 2026
coderabbitai[bot]

This comment was marked as resolved.

…ules

- Extract server helper functions into tests/fixtures/client_streaming/server.rs
- Extract types and StreamTestEnvelope into tests/fixtures/client_streaming/types.rs
- Clean up imports and mod declarations in tests/fixtures/client_streaming.rs

This refactor improves organization and modularity of client streaming test fixtures.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Client streaming: ResponseStream API for multi-packet responses Client streaming: implement ResponseStream for multi-packet support Feb 18, 2026
coderabbitai[bot]

This comment was marked as resolved.

…ope fields

Add detailed doc comments to the `id`, `correlation_id`, and `payload` fields of the `StreamTestEnvelope` struct to clarify their roles and behavior, improving code readability and maintainability in the client_streaming tests.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Client streaming: implement ResponseStream for multi-packet support Refactor error messages and document client streaming struct Feb 18, 2026
@leynos leynos changed the title Refactor error messages and document client streaming struct Client streaming: ResponseStream API for multi-packet responses Feb 18, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 19, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check resolution status of all review comments

echo "=== 1. Check if StreamTerminated is used in ResponseStream ==="
rg -n "StreamTerminated" src/client/response_stream.rs
echo "Exit code: $?"

echo -e "\n=== 2. Check Runtime creation in BDD steps ==="
rg -n "Runtime::new\(\)" tests/steps/client_streaming_steps.rs

echo -e "\n=== 3. Check Packet doc example (Message comment) ==="
sed -n '80,120p' src/app/envelope.rs | rg -n "Message"

echo -e "\n=== 4. Check correlation validation test assertions ==="
rg -n "response_stream_validates_correlation_id" src/client/tests/streaming.rs -A 35 | grep -E "(is_terminated|next.*None)"

echo -e "\n=== 5. Check decode error test exists ==="
rg -n "decode.*error|malformed|surfaces_decode_error" src/client/tests/streaming.rs | head -5

echo -e "\n=== 6. Check BDD acronym expansion ==="
rg -n "behaviour-driven development|BDD" docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md | head -3

echo -e "\n=== 7. Check TCP acronym expansion at first use ==="
rg -n "^.*TCP" docs/execplans/10-3-1-client-response-stream-and-response-multipacket.md | head -3

Length of output: 1181


🏁 Script executed:

#!/bin/bash
# Need more detailed checks

echo "=== Check full client_streaming_steps.rs for async/runtime patterns ==="
cat tests/steps/client_streaming_steps.rs

echo -e "\n=== Check full correlation validation test ==="
rg -n "response_stream_validates_correlation_id" src/client/tests/streaming.rs -A 40

echo -e "\n=== Verify ClientError::StreamTerminated variant still exists ==="
rg -n "StreamTerminated" src/client/error.rs

Length of output: 5735


🏁 Script executed:

#!/bin/bash
# Check if StreamTerminated variant exists and get context
echo "=== Full ClientError enum from error.rs ==="
rg -n "enum ClientError" src/client/error.rs -A 80

echo -e "\n=== Check documentation mentioning StreamTerminated ==="
rg -n "StreamTerminated" docs/

Length of output: 3633


@leynos

Excellent news! All review comments have now been fully resolved. Here's the complete status:

✅ All Resolved (7 out of 7)

Overall Comment 1 (ClientError::StreamTerminated): ✅ RESOLVED
The variant no longer exists in src/client/error.rs. The execplan documentation (line 262) states: "StreamTerminated error variant was considered but rejected". The design decision was to use Poll::Ready(None) for fused stream behaviour rather than introducing an error variant.

Overall Comment 2 (BDD per-step runtime): ✅ RESOLVED
The file now uses a shared runtime approach. The comment at the top states: "Steps run async world methods on the shared runtime stored in ClientStreamingWorld rather than constructing a per-step runtime." All steps now call world.block_on() instead of creating Runtime::new() per invocation.

Individual Comment 1 (Packet doc example): ✅ RESOLVED
Line 22 of src/app/envelope.rs includes the clarifying comment: "Message is auto-implemented via the blanket impl for Encode + BorrowDecode types."

Individual Comment 2 (Correlation test assertions): ✅ RESOLVED
Lines 137-148 of src/client/tests/streaming.rs include both termination checks:

  • stream.is_terminated() assertion
  • stream.next().await returning None assertion

Individual Comment 3 (Decode error test): ✅ RESOLVED
Test response_stream_surfaces_decode_error exists at line 214 with complete termination verification.

Individual Comment 4 (BDD acronym): ✅ RESOLVED
Line 36 of the execplan document reads: "behaviour-driven development (BDD)".

Individual Comment 5 (TCP acronym): ✅ RESOLVED
Line 21 of the execplan document reads: "Transmission Control Protocol (TCP)".

All outstanding review items have been addressed. The PR is now fully compliant with the review requirements.

@leynos leynos merged commit 10daa36 into main Feb 19, 2026
6 checks passed
@leynos leynos deleted the client-response-stream-multipacket-h12vcv branch February 19, 2026 12:12
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