Skip to content

feat!: overhaul the public bot API ahead of 1.0 - #852

Merged
jlucaso1 merged 2 commits into
mainfrom
feat/bot-api-overhaul
Jun 11, 2026
Merged

feat!: overhaul the public bot API ahead of 1.0#852
jlucaso1 merged 2 commits into
mainfrom
feat/bot-api-overhaul

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

Why

A rigorous pass over our public API against serenity 0.12 and teloxide surfaced a set of first-mile problems worth fixing before 1.0, while breaking changes are still cheap.

The two biggest ones were embarrassing in hello world itself: the README quick start did not compile (the handler receives Arc<Event> but the example matched on Event, E0308, verified empirically), and the lifecycle required bot.run().await?.await? where the first .await and the first ? were literally free of function (the old run_graph had zero await points and no error path).

On top of that, a survey of real consumers on GitHub (moltis, picobot, Papo, zeroclaw) showed every single one declaring 5 to 7 of our crates as separate git dependencies (whatsapp-rust + wacore + wacore-binary + waproto + both satellites), because the README taught imports from the satellite crates and wacore/wacore-binary were not reachable through the main crate.

What changed

Lifecycle. Bot::run(self) now runs the bot on the current task with a single await, and Bot::spawn(self) starts it in the background and returns a BotHandle with client(), a graceful shutdown() (flushes the device snapshot, buffered receipts and message secrets via Client::disconnect, then waits for the loop to exit), and abort() as a documented escape hatch. Awaiting the handle resolves to () instead of leaking futures::channel::oneshot::Canceled. Dropping the handle still aborts the task (the e2e harness relies on that).

Builder defaults. With the default cargo features, transport, HTTP client and runtime start as Provided (Tokio WebSocket, ureq, Tokio), so only the backend has to be chosen; the with_* setters are now available in every typestate, so they also work as overrides. The typestate markers got names (MissingBackend, MissingTransport, MissingHttpClient, MissingRuntime), so a missing-field compile error now says which field is missing. with_backend takes impl Backend + 'static (no caller-side Arc), with with_backend_arc for an already-shared backend. BotBuilderError lost its fake Other(anyhow) variant and now carries the typed Store(StoreError) that build() can actually produce.

Event handlers. Registrations accumulate instead of silently replacing the previous handler. New typed registrars cover the common path: on_message (receives a ready MessageContext), on_qr_code, on_pair_code, on_connected, on_logged_out, with on_event/on_event_for unchanged as the catch-all. with_event_handler registers a struct-based EventHandler directly on the bus for stateful handlers (kills the clone-dance that closure captures force on consumers). The bus still skips materializing events nobody wants: the adapter registers the union of all handler interests (EventInterest::union) and filters per handler at dispatch.

Messaging sugar. wa::Message::text(...) and wa::Message::text_with_context(...) via the new MessageBuilderExt in wacore::proto_helpers, ctx.reply(...) and ctx.reply_quoting(...) on MessageContext, and Client::send_text(jid, text). The raw protobuf path stays untouched for advanced cases.

Single-dependency consumption. The crate now re-exports wacore, wacore_binary and waproto wholesale, so every path is reachable as whatsapp_rust::wacore::... and a git consumer needs exactly one dependency line. UreqHttpClient moved from the misplaced whatsapp_rust::transport re-export to whatsapp_rust::http. A new whatsapp_rust::prelude covers the common bot path in one import (including wa as the protobuf alias).

Docs that cannot rot. The README was rewritten around the new API (quick start is one dependency plus tokio) and is now included as crate docs via #![doc = include_str!], so both README examples compile as doctests on every CI run. The demo main.rs showcases the typed registrars, ctx.reply_quoting, and ctrl-c handling via handle.shutdown().

Breaking changes and migration

  • bot.run().await?.await? becomes bot.run().await (foreground) or let handle = bot.spawn(); (background, what Veloz-style consumers want). run consumes the bot; grab bot.client() before, or use handle.client().
  • .with_backend(arc_backend) becomes .with_backend_arc(arc_backend); plain stores can drop the Arc::new entirely.
  • use whatsapp_rust::transport::UreqHttpClient becomes use whatsapp_rust::http::UreqHttpClient.
  • bot::Missing was replaced by the named markers; code that spelled out builder types must update.
  • BotBuilderError::Other is gone; the only variant is Store(StoreError).
  • Registering two handlers now runs both (previously the second silently replaced the first).

The send/connect anyhow-to-typed-errors migration from the same review is intentionally staged as a follow-up PR to keep this one reviewable.

Validation

  • cargo clippy --workspace --all-targets -- -D warnings clean (also fixed a latent chrono::Local::now disallowed-method hit in the benchmark example that workspace-level feature unification exposes).
  • cargo test -p whatsapp-rust -p wacore: 1769 unit tests plus doctests green, including the two README examples as compile-checked doctests.
  • wasm32 guard locally: cargo build -p whatsapp-rust --lib --target wasm32-unknown-unknown --no-default-features --features debug-diagnostics builds (exercises the no-defaults typestate path).
  • e2e harness and benchmark example migrated and compiling under workspace clippy.

- Bot::run(self) is a single await and Bot::spawn() returns a BotHandle with graceful shutdown()
- the builder pre-fills transport/HTTP/runtime from the default features, so only the backend is required
- event handlers accumulate instead of silently replacing, with typed registrars (on_message, on_qr_code, on_pair_code, on_connected, on_logged_out)
- MessageContext gains reply/reply_quoting, Client gains send_text, wa::Message gains text builders
- whole-crate re-exports (wacore, wacore_binary, waproto) so git consumers need a single dependency
- README quick start compiles again and is enforced as a doctest
@coderabbitai

coderabbitai Bot commented Jun 11, 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

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 784933f7-da33-40fc-99c8-e5b4476b990c

📥 Commits

Reviewing files that changed from the base of the PR and between 5401628 and b222b51.

📒 Files selected for processing (1)
  • README.md

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • New builder callbacks for common events (QR, pairing, connected, logged-out, messages)
    • Reply conveniences and plain-text send helper for simpler responses and quoting
    • Background run/spawn with graceful shutdown and access to client handle
    • Single-import prelude for easier setup; crate re-exports exposed for convenience
    • Message construction helpers and event-interest unioning for handler composition
  • Documentation

    • README quick-start replaced with the new builder flow and usage examples (doctested)

Walkthrough

Refactors Bot construction, adds multi-handler registration routed through a CallbackBusAdapter, changes lifecycle to consuming run()/spawn()/shutdown(), adds MessageContext reply helpers and Client::send_text, introduces a prelude and wacore re-exports, and updates examples/tests/README to the new builder/run patterns.

Changes

Bot Builder and Event System Refactor

Layer / File(s) Summary
Message and event helper primitives
wacore/src/types/events.rs, wacore/src/proto_helpers.rs, src/bot.rs
Adds EventInterest::union(), MessageBuilderExt::text/text_with_context(), and MessageContext::reply() / reply_quoting().
Bot typestate and lifecycle refactor
src/bot.rs
Replaces single Missing marker with MissingBackend/Transport/HttpClient/Runtime and feature defaults. Changes Bot::run(self) consuming, returning (). Adds Bot::spawn() returning BotHandle with shutdown() and client() methods; BotHandle future resolves to (). BotBuilderError now surfaces Store(StoreError).
Event handler routing and dispatch
src/bot.rs
Replaces single event handler with event_handlers: Vec<RegisteredHandler> and raw_handlers. Introduces combined_interest() and CallbackBusAdapter that spawns a task per matching handler. Updates start_background wiring.
Builder ergonomics and convenience methods
src/bot.rs, src/lib.rs, src/http.rs, src/transport.rs
with_backend accepts impl Backend + 'static; adds with_backend_arc. Adds on_message, on_qr_code, on_pair_code, on_connected, on_logged_out, and with_event_handler. Adds crate-level README doctest, wacore/wacore_binary re-exports, and a prelude module. Adjusts transport/http conditional re-exports.
Client convenience API
src/send.rs
Adds Client::send_text(to, text) that builds a wa::Message and sends with default options.
Executable and examples migration
src/main.rs, examples/benchmark.rs
Switches to whatsapp_rust::prelude::*, uses new builder callbacks and reply helpers (ctx.reply, ctx.reply_quoting, ctx.react), inlines backend/store creation, and updates run/spawn/shutdown control flow.
Tests and fixtures updates
src/features/presence.rs, src/types/enc_handler.rs, tests/e2e/src/lib.rs, src/bot.rs tests
Updates tests to use with_backend_arc where needed, adapts e2e harness to spawn background bot, and adds tests for handler accumulation, combined_interest, defaults-only build, and from_arc cloning behavior.
Documentation
README.md
Rewords Features bullets, replaces Quick Start with prelude-based builder example, and adds fenced text marker in Project Structure.

Sequence Diagram

sequenceDiagram
  participant UserCode as Client app
  participant BotBuilder
  participant Bot
  participant EventBus
  participant CallbackBusAdapter
  participant RegisteredHandler
  UserCode->>BotBuilder: configure with_backend / on_* callbacks
  BotBuilder->>Bot: build()
  Bot->>EventBus: start_background()
  Bot->>EventBus: register CallbackBusAdapter (with combined_interest)
  EventBus->>CallbackBusAdapter: incoming Event
  CallbackBusAdapter->>RegisteredHandler: spawn task for interested handler
  RegisteredHandler->>Client: invoke user callback (MessageContext)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Note: ensure tests compiling against feature defaults and typestate transitions are validated; these changes alter public construction and runtime behavior and must be exercised by CI.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat!: overhaul the public bot API ahead of 1.0' directly and accurately summarizes the main breaking changes to the public bot API, which is the core focus of this large changeset.
Description check ✅ Passed The description is comprehensive and clearly related to the changeset, providing detailed context on lifecycle changes, builder defaults, event handlers, messaging helpers, and single-dependency consumption improvements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bot-api-overhaul

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 and usage tips.

@codspeed-hq

codspeed-hq Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 140 untouched benchmarks


Comparing feat/bot-api-overhaul (b222b51) with main (fd0e171)

Open in CodSpeed

@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.

1 issue found across 13 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread README.md Outdated
The background example calls tokio::signal::ctrl_c(), which a consumer copying the snippet would be missing (our doctests inherit the feature from the crate's own tokio dependency, so they could not catch it).
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.

1 participant