Skip to content

refactor(events): seal ServerAck with non_exhaustive + bon builder - #1002

Merged
jlucaso1 merged 2 commits into
mainfrom
claude/whatsapp-rust-pr-review-wjppqi
Jul 7, 2026
Merged

refactor(events): seal ServerAck with non_exhaustive + bon builder#1002
jlucaso1 merged 2 commits into
mainfrom
claude/whatsapp-rust-pr-review-wjppqi

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What

First step of the pre-1.0 event-payload API freeze (follow-up to the stability policy documented in #1000). It introduces bon and applies the target sealing pattern to a single payload, ServerAck, as a proof of concept. The remaining ~44 payload structs will follow the same shape in later PRs.

The pattern

Two orthogonal, zero-overhead mechanisms, one per side of the problem:

  • Read side — #[non_exhaustive]. Downstream can no longer exhaustively destructure the struct (must use a .. rest), so adding a field later is non-breaking for consumers. This is the standard std/tokio/hyper approach.
  • Construct side — #[derive(bon::Builder)]. #[non_exhaustive] blocks cross-crate struct literals, so the library needs a construction path. bon gives a compile-time typestate builder that monomorphizes to the same code as a struct literal (no runtime cost). Required fields are checked at compile time; Option fields get maybe_* setters.

handle_ack_response now builds via ServerAck::builder().id(...).maybe_class(...)...build(), passing the Option attributes through directly instead of unwrap_or_default().

Why bon

It is the current state-of-the-art builder derive: zero runtime overhead, compile-time required-field checks, maybe_* setters for optionals, and field additions stay non-breaking. It is a compile-time-only dependency, so there is no runtime or meaningful binary-size impact (the binary-size job will confirm).

Verification

  • ack_miss_path_does_not_heap_allocate stays green: the builder adds no allocation and is still gated behind has_handler_for, so the hot miss path is untouched.
  • test_ack_dispatches_server_ack_event green: the builder produces the same struct.
  • The seal was verified by hand — reverting the parser to a struct literal fails to compile with E0639: cannot create non-exhaustive struct, confirming cross-crate construction now goes through the builder.
  • cargo fmt --check and cargo clippy -p wacore -p whatsapp-rust --tests clean.

Follow-ups (not in this PR)

  • Apply the same pattern to the remaining payload structs and convert the three inline Event variants (PairingQrCode / PairingCode / PairingCodeRefresh) to newtypes.
  • Add a trybuild compile-fail test to lock the seal permanently.
  • Optionally migrate EventInterest off u64 (currently 58/64 kinds used) as part of the freeze.

First step of the pre-1.0 event-payload API freeze. Introduces bon and
applies the target pattern to ServerAck as the proof of concept:

- #[non_exhaustive] seals the struct so downstream can no longer
  exhaustively destructure it, making future field additions
  non-breaking for consumers.
- #[derive(bon::Builder)] gives a zero-cost typestate builder as the
  construction path, since non_exhaustive blocks cross-crate struct
  literals. Required fields are checked at compile time; Option fields
  get maybe_* setters, so the ack parser passes the Option through
  directly instead of unwrap_or_default().

handle_ack_response now builds via ServerAck::builder(). The alloc
guard (ack_miss_path_does_not_heap_allocate) stays green: the builder
adds no allocation and is still gated behind has_handler_for.

The remaining ~44 payload structs will follow the same pattern.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0424a7e1-79d5-48e6-8fdb-90a9a42e4ed7

📥 Commits

Reviewing files that changed from the base of the PR and between cea45a8 and 1814ef8.

📒 Files selected for processing (2)
  • AGENTS.md
  • wacore/src/types/events.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Improved server acknowledgement handling by switching acknowledgement payloads to builder-based construction, with proper handling of optional fields.
    • Event payloads are now marked as non-exhaustive to support safer future extensions without breaking consumers.
  • Documentation

    • Updated guidance for constructing event payloads using generated builders and representing absent values with Option<T>.
  • Chores

    • Added a shared bon dependency to support the new builder-based payload pattern.

Walkthrough

Bon is added as a shared workspace dependency and referenced from wacore. ServerAck now derives bon::Builder and is #[non_exhaustive]. The client ack handler now constructs ServerAck through the generated builder API, and the event payload guidance was updated to match.

Changes

bon Builder Adoption for ServerAck

Layer / File(s) Summary
Dependency wiring for bon
Cargo.toml, wacore/Cargo.toml
Adds bon as a shared workspace dependency with std enabled and wires it into wacore as a workspace dependency.
ServerAck sealing and builder usage
wacore/src/types/events.rs, src/client/node_io.rs, AGENTS.md
Updates ServerAck to derive bon::Builder and become #[non_exhaustive], rewrites handle_ack_response to use ServerAck::builder() with maybe_* setters, and aligns the event payload guidance with the new builder-based pattern.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NodeIO as handle_ack_response
  participant ServerAck as ServerAck::builder()

  NodeIO->>ServerAck: builder()
  NodeIO->>ServerAck: id(id)
  NodeIO->>ServerAck: maybe_class(class)
  NodeIO->>ServerAck: maybe_from(from)
  NodeIO->>ServerAck: maybe_timestamp(t)
  NodeIO->>ServerAck: maybe_error(error)
  ServerAck-->>NodeIO: built ServerAck
Loading

Suggested labels: breaking-change, api-design

Suggested reviewers: cubic-dev-ai, greptile-apps

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: sealing ServerAck with non_exhaustive and a bon builder.
Description check ✅ Passed The description is directly related and accurately explains the ServerAck sealing and builder changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/whatsapp-rust-pr-review-wjppqi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces the first step of a pre-1.0 API-freeze strategy for event payloads, applying #[non_exhaustive] + #[derive(bon::Builder)] to ServerAck as the proof-of-concept pattern, and updating both AGENTS.md and the Event doc-comment to document the rolling rollout.

  • ServerAck sealed: gains #[non_exhaustive] (blocks cross-crate struct literals) and a bon typestate builder so consumers construct it via ServerAck::builder()…build() — the only internal construction site in node_io.rs is migrated accordingly, preserving the hot-miss-path allocation gate.
  • bon added as a workspace dependency: pinned at 3.9.3 with default-features = false, features = ["std"], consistent with the workspace's existing explicit-feature-gating pattern; the lockfile confirms the resolved dependency graph.
  • Documentation updated: AGENTS.md convention bullet and the Event stability doc-comment both now describe the new sealing pattern and note that not every payload carries the attribute yet.

Confidence Score: 5/5

Safe to merge — a focused, mechanical refactor that adds no new logic, preserves the hot-miss-path allocation gate, and correctly applies the #[non_exhaustive] + bon::Builder seal within the same crate.

The change is purely additive on the type-system side and mechanical on the construction side: the builder call produces the same struct as the replaced literal, maybe_* setters accept the existing Option expressions unchanged, and the only internal construction site is correctly migrated. Documentation and AGENTS.md are kept in sync. No logic paths are altered.

No files require special attention.

Important Files Changed

Filename Overview
wacore/src/types/events.rs Adds #[non_exhaustive] and #[derive(bon::Builder)] to ServerAck, and updates the Event stability doc-comment to explain the rolling-out sealing approach; implementation is correct.
src/client/node_io.rs Migrates ServerAck construction from a struct literal to the bon builder; maybe_* setters correctly accept the existing Option expressions, and the hot-miss-path gating on has_handler_for is preserved.
AGENTS.md Updates the event-payload convention bullet to document the new #[non_exhaustive] + bon::Builder sealing approach, including the rolling-out caveat; consistent with the code changes.
Cargo.toml Adds bon 3.9.3 to the workspace with default-features = false, features = ["std"], matching the workspace-wide pattern of explicit feature gating; no issues.
wacore/Cargo.toml Adds bon from the workspace to wacore's dependencies; straightforward one-line addition.
Cargo.lock Lockfile adds bon 3.9.3 and its proc-macro crate bon-macros 3.9.3 with the expected dependency graph; no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["handle_ack_response()"] --> B{has_handler_for\nServerAck?}
    B -- No --> C["Hot-miss path:\nno allocation"]
    B -- Yes --> D["ServerAck::builder()\n.id(…)\n.maybe_class(…)\n.maybe_from(…)\n.maybe_timestamp(…)\n.maybe_error(…)\n.build()"]
    D --> E["Event::ServerAck(ack)"]
    E --> F["event_bus.dispatch(…)"]

    subgraph "wacore (same crate)"
        G["#[non_exhaustive]\n#[derive(bon::Builder)]\npub struct ServerAck"]
        G -->|"builder generated\nin same crate"| D
    end

    subgraph "External crates"
        H["Consumer reads fields\nack.id / ack.class / …"]
        I["Cross-crate struct literal\n→ E0639 compile error"]
    end

    F --> H
    D -.->|"#[non_exhaustive] blocks"| I
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["handle_ack_response()"] --> B{has_handler_for\nServerAck?}
    B -- No --> C["Hot-miss path:\nno allocation"]
    B -- Yes --> D["ServerAck::builder()\n.id(…)\n.maybe_class(…)\n.maybe_from(…)\n.maybe_timestamp(…)\n.maybe_error(…)\n.build()"]
    D --> E["Event::ServerAck(ack)"]
    E --> F["event_bus.dispatch(…)"]

    subgraph "wacore (same crate)"
        G["#[non_exhaustive]\n#[derive(bon::Builder)]\npub struct ServerAck"]
        G -->|"builder generated\nin same crate"| D
    end

    subgraph "External crates"
        H["Consumer reads fields\nack.id / ack.class / …"]
        I["Cross-crate struct literal\n→ E0639 compile error"]
    end

    F --> H
    D -.->|"#[non_exhaustive] blocks"| I
Loading

Reviews (2): Last reviewed commit: "docs(events): align stability policy wit..." | Re-trigger Greptile

Comment thread wacore/src/types/events.rs
Greptile flagged that AGENTS.md and the Event doc still said payloads
stay constructible / non_exhaustive is deferred, which contradicts
sealing ServerAck in this PR. Update both to document the actual policy:
seal payloads with #[non_exhaustive] + a bon builder, construct via the
builder, and note the seal is rolling out per struct.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Auto-approved: Adds bon dependency, applies builder pattern + non_exhaustive to ServerAck, updates docs. No logic changes, low impact.

Re-trigger cubic

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 10.81 MiB 10.81 MiB +128 B (+0.00%) 🔺
bin .text 8.81 MiB 8.81 MiB +128 B (+0.00%) 🔺
bin allocated (text+data+bss) 10.81 MiB 10.81 MiB 0
llvm-lines wacore 504,323 504,323 0
llvm-lines wacore copies 17,278 17,278 0
llvm-lines whatsapp-rust lib 757,825 758,006 +181 (+0.02%) 🔺
llvm-lines whatsapp-rust lib copies 24,586 24,596 +10 (+0.04%) 🔺
deps crates (Cargo.lock) 466 468 +2 (+0.43%) 🔺
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.63 MiB 1.63 MiB +125 B (+0.01%) 🔺
.text wacore 531.47 KiB 531.47 KiB 0
.text wacore_binary 157.70 KiB 157.70 KiB 0
.text wacore_libsignal 178.73 KiB 178.73 KiB 0
.text wacore_appstate 156.45 KiB 156.45 KiB 0
.text wacore_noise 26.05 KiB 26.05 KiB 0
.text waproto 1.60 MiB 1.60 MiB 0
.text whatsapp_rust_sqlite_storage 512.98 KiB 512.98 KiB 0
.text whatsapp_rust_tokio_transport 43.61 KiB 43.61 KiB 0
.text whatsapp_rust_ureq_http_client 10.47 KiB 10.47 KiB 0
.text std 1.00 MiB 1.00 MiB 0
.text other deps 2.95 MiB 2.95 MiB 0
Top movers (cargo-bloat attribution)
Crate main PR Δ
rustix 1.88 KiB 191 B -1.69 KiB (-90.08%)
buffa_descriptor 2.98 KiB 4.67 KiB +1.69 KiB (+56.87%)

Baseline: 37effa5d2 (latest main run) · Head: 43affcb9e · Graphs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants