Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/adr-004-pluggable-protocol-codecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,18 @@ pub trait FrameCodec: Send + Sync + 'static {
and explicit `try_from` conversions, suggesting a need for shared parsing
helpers to reduce boilerplate.

## Implementation guidance

- Add codec-specific tests that exercise encoder/decoder round-trips, payload
extraction, correlation identifiers, and maximum frame length enforcement.
- Use shared example codecs in `wireframe::codec::examples` to drive regression
and property-based tests without duplicating framing logic.
- Prefer `wireframe_testing` helpers that work with custom codecs, so test
harnesses do not assume length-delimited framing.
- Validate observability signals (logs, metrics, and protocol hooks) for codec
failures and recovery policies using the test observability harness once
available.[^adr-006]
Comment thread
leynos marked this conversation as resolved.

## Known Risks and Limitations

- Associated types avoid RPITIT for `FrameCodec`, but the overall design still
Expand Down Expand Up @@ -254,3 +266,5 @@ preserves existing behaviour through a default codec while enabling protocol
specificity and reusability. By tying buffer sizing and maximum frame length to
codec configuration, the design keeps transport-level constraints close to the
framing rules that define them.

[^adr-006]: See [adr-006-test-observability.md](adr-006-test-observability.md).
114 changes: 114 additions & 0 deletions docs/adr-006-test-observability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Architectural decision record (ADR) 006: test observability harness

## Status

Proposed.

## Date

2026-01-02.

## Context and Problem Statement

Wireframe relies on logs, metrics, and protocol hooks for runtime
observability. Upcoming codec hardening work adds structured errors and
recovery policies that must be verified alongside behaviour. Today, tests can
capture logs via `wireframe_testing::logger`, but metrics require ad hoc global
exporters, and there is no shared helper for asserting instrumentation. This
makes telemetry assertions inconsistent, brittle, and hard to reuse across
downstream crates.

A dedicated test observability harness is needed, so tests can assert on
logging and metrics deterministically without depending on external exporters
or global state leaks.

## Decision Drivers

- Provide deterministic, per-test observability assertions.
- Avoid external services or network dependencies in tests.
- Keep the harness reusable for downstream crates and example codecs.
- Guard global registries to prevent cross-test interference.

## Requirements

### Functional requirements

- Capture logs and metrics for a single test run.
- Provide helper APIs for inspecting log records and metric samples.
- Support async tests and background tasks without losing telemetry.

### Technical requirements

- Avoid environment variable mutations in tests.
- Use in-process recorders and restore prior global state on drop.
- Keep dependencies minimal and aligned with existing crates.

## Options Considered

### Option A: keep ad hoc log capture (status quo)

Continue using `logtest` directly in tests and rely on manual, ad hoc setup for
metrics.

### Option B: add a unified test observability harness (preferred)

Provide a `wireframe_testing::observability` module that installs a scoped log
capture and metrics recorder, returning a guard with inspection helpers.

### Option C: rely on external exporters in integration tests

Use Prometheus exporters or external telemetry services and parse their output
in tests.

## Decision Outcome / Proposed Direction

Adopt Option B and add a scoped test observability harness to
`wireframe_testing`. The harness should compose with the existing logger guard
and provide a metrics recorder suitable for assertions in unit, integration,
and behavioural tests.

## Approach

- Add an `ObservabilityHandle` that installs log capture and a metrics recorder
when constructed and restores previous globals on drop.
- Reuse `logtest` for logs and introduce a metrics recorder based on
`metrics-util` so tests can query counters and gauges without external
exporters.
- Provide helper methods to clear state between assertions and to filter by
labels when verifying counters.
- Serialize access with a global lock to avoid cross-test interference, and
document that tests using the handle should not run concurrently.
- For parallel test runners, run observability-heavy test binaries with
`--test-threads=1` or gate observability tests behind a shared fixture to
prevent interleaving.

## Consequences

- Tests can assert on instrumentation for codec errors and recovery policies
without bespoke setup.
- Global recorder access requires serialization, which may reduce parallelism
for observability-heavy suites, such as forcing `--test-threads=1` for the
affected test binary. See the mitigation guidance in the approach.
- The testing crate gains additional dependency surface for metrics capture.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
## Roadmap

### Phase 1: Observability capture primitives

- Step: Introduce the harness in `wireframe_testing`.
- Task: Add `ObservabilityHandle` and integrate with `LoggerHandle`.
- Task: Provide metric snapshot helpers for counters and gauges.
- Task: Document usage and thread-safety constraints.

### Phase 2: Codec and recovery assertions

- Step: Wire codec tests to the harness.
- Task: Add helpers for asserting codec error counters and log fields.
- Task: Update codec regression tests to use the new helpers.

## Mitigation and rollback

- Keep the harness behind a feature flag if global recorder conflicts with
downstream test environments.
- If metrics capture proves flaky, fall back to log-based assertions while
refining recorder isolation.
14 changes: 14 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,18 @@ integration boundaries.
- [ ] Measure fragmentation overhead versus unfragmented paths.
- [ ] Record memory allocation baselines for payload wrapping and decoding.

### 9.7. Codec test harness and observability

- [ ] 9.7.1. Extend `wireframe_testing` with codec-aware drivers that can run
`WireframeApp` instances configured with custom `FrameCodec` values.
- [ ] 9.7.2. Add codec fixtures in `wireframe_testing` for generating valid and
invalid frames, including oversized payloads and correlation metadata.
- [ ] 9.7.3. Introduce a test observability harness in `wireframe_testing` that
captures logs and metrics per test run for asserting codec failures and
recovery policies.[^adr-006]
- [ ] 9.7.4. Add regression tests in `wireframe_testing` for the `CodecError`
taxonomy and recovery policy behaviours defined in 9.1.2. Requires 9.1.2.

## 10. Wireframe client library foundation

This phase delivers a first-class client runtime that mirrors the server's
Expand Down Expand Up @@ -569,3 +581,5 @@ and usability.
[message-versioning.md](message-versioning.md).
[^adr-005]: See
[adr-005-serializer-abstraction.md](adr-005-serializer-abstraction.md).
[^adr-006]: See
[adr-006-test-observability.md](adr-006-test-observability.md).
45 changes: 45 additions & 0 deletions docs/users-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,51 @@ helpers `send_response` and `send_response_framed` (or
variants when encoding or I/O fails, and the connection closes after ten
consecutive deserialization errors.[^6][^7]

### Custom frame codecs

Custom protocols supply a `FrameCodec` implementation to describe their framing
rules. The codec owns the Tokio `Decoder` and `Encoder` types, while Wireframe
uses the trait surface to map frames to payload bytes and correlation data.

A codec implementation must:

- Define a `Frame` type and paired decoder/encoder implementations that return
`std::io::Error` on failure.
- Return only the logical payload bytes from `frame_payload` so metadata parsing
and deserialisation run against the right buffer.
- Wrap outbound payloads with `wrap_payload`, adding any protocol headers or
metadata required by the wire format.
- Provide `correlation_id` when the protocol stores it outside the payload;
Wireframe only uses this hook when the deserialized envelope is missing a
correlation identifier.
- Report `max_frame_length`, which clamps inbound frames and seeds default
fragmentation limits.

Install a custom codec with `with_codec`. The builder resets fragmentation to
the codec-derived defaults, so override fragmentation afterwards if the
protocol uses a different budget. When a framed stream is already available,
use `send_response_framed_with_codec`, so responses pass through
`FrameCodec::wrap_payload`.
Comment thread
leynos marked this conversation as resolved.

Assume `MyCodec` implements `FrameCodec`:

```rust,no_run
use std::sync::Arc;

use wireframe::app::{Envelope, Handler, WireframeApp};

struct MyCodec;

let handler: Handler<Envelope> = Arc::new(|_: &Envelope| Box::pin(async {}));

let app = WireframeApp::new()?
.with_codec(MyCodec)
.route(1, handler)?;
```

See `examples/hotline_codec.rs` and `examples/mysql_codec.rs` for complete
implementations.
Comment thread
leynos marked this conversation as resolved.

## Packets, payloads, and serialization

Packets drive routing. Implement the `Packet` trait (or use the bundled
Expand Down
Loading
Loading