-
Notifications
You must be signed in to change notification settings - Fork 0
docs(errors): document RejectionStanza and ServerError's response field #499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,8 @@ As of PR #1100, every wrapping variant that used to be `#[error(transparent)]` i | |
|
|
||
| As of PR #1195, `ErrorChainExt` also answers `http_status()` — the HTTP status code behind a refused download, upload, sticker-pack fetch, or app-version fetch, recovered from a [`HttpStatusError`](#httpstatuserror) node the same way `server_rejection()` recovers an IQ rejection. This is the one place the "lower-level APIs … may still surface `anyhow::Error` directly" caveat above gets a typed escape hatch: `download`/`upload` still return `anyhow::Error`, but the status inside that error is now recoverable by type instead of by parsing `Display` text. See [Error chain recovery](#error-chain-recovery) below. | ||
|
|
||
| As of PR #1257, `IqError::ServerError` also carries `response: RejectionStanza` — the `type="error"` stanza itself, handed over whole the same way a `type="result"` response already was, alongside the four fields this crate parses off it (`code`, `text`, `error_type`, `backoff`). See [`RejectionStanza`](#rejectionstanza) below. | ||
|
|
||
| ## Error hierarchy | ||
|
|
||
| ``` | ||
|
|
@@ -147,6 +149,33 @@ pub struct ServerRejection<'a> { | |
|
|
||
| Borrowed from whichever error in the chain carried it, so recovering one costs no allocation. | ||
|
|
||
| ### RejectionStanza | ||
|
|
||
| Added in PR #1257. `IqError::ServerError`'s `response` field: the `<iq type="error">` stanza the receive path decoded, kept as-is rather than reduced to the four fields [`ServerRejection`](#serverrejection) exposes above. It wraps the same `Arc<OwnedNodeRef>` the success path already hands back, so attaching it to the error costs one refcount bump, not a copy. | ||
|
|
||
| ```rust | ||
| pub struct RejectionStanza(Arc<OwnedNodeRef>); | ||
|
|
||
| impl RejectionStanza { | ||
| /// The preserved node behind its refcount, for a caller that wants to keep or share it. | ||
| pub fn as_arc(&self) -> &Arc<OwnedNodeRef>; | ||
| /// Takes the preserved node out, consuming the wrapper. | ||
| pub fn into_arc(self) -> Arc<OwnedNodeRef>; | ||
| } | ||
|
|
||
| impl From<Arc<OwnedNodeRef>> for RejectionStanza { /* ... */ } | ||
|
|
||
| impl Deref for RejectionStanza { | ||
| type Target = OwnedNodeRef; | ||
| } | ||
| ``` | ||
|
|
||
| `ServerRejection`'s four fields cover what WA Web's own `parseIqResponse` reads off an error; `RejectionStanza` is the escape hatch for everything that parser (and this crate's) leaves unread — further `<iq>`/`<error>` attributes, `<error>` children such as XMPP application-condition elements, and the raw bytes, which are the only faithful material for logging or replaying a rejection. `Deref<Target = OwnedNodeRef>` (see [`OwnedNodeRef`](/advanced/binary-protocol#ownednoderef-yoke-zero-copy)) keeps every node accessor reachable directly on the wrapper — `response.tag()`, `response.attrs()`, `response.get_optional_child(...)`, or `response.get()` for the underlying `NodeRef`. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This paragraph combines parser behavior, preserved data categories, logging and replay implications, dereference mechanics, and accessor examples into two dense sentences. Split these independent ideas into concise sentences so the new reference follows the project's one-idea-per-sentence requirement. AGENTS.md reference: AGENTS.md:L24-L25 Useful? React with 👍 / 👎.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same style question CodeRabbit raised on this PR (line 14) — skipping for the same reason: the surrounding unchanged content on this page is consistently dense, third-person, rationale-heavy prose (e.g. the Generated by Claude Code |
||
|
|
||
| `Debug` is overridden to print only the tag (`<iq>`), not the stanza's contents. This matters because background IQ failures on the connect path (the post-connect active IQ, props, blocklist, privacy settings) are logged with `{e:?}` at warn level, and an error stanza's attributes or children can carry a JID — a straight derive would have written that into production logs where before only the four summarized fields went. Read the node explicitly (`response.get()`, `response.attrs()`, …) when you need its contents. | ||
|
|
||
| Re-exported from the crate root as `whatsapp_rust::RejectionStanza`, and from `whatsapp_rust::prelude`. | ||
|
|
||
| ### HttpStatusError | ||
|
|
||
| Added in PR #1195, in `whatsapp_rust::http`. Carries the status of an HTTP exchange the client refused — the source node `http_status()` looks for. | ||
|
|
@@ -557,7 +586,13 @@ pub enum IqError { | |
| #[error("received disconnect node during IQ wait: {0:?}")] | ||
| Disconnected(Box<Node>), | ||
| #[error("received a server error response: code={code}, text='{text}'")] | ||
| ServerError { code: u16, text: String, error_type: Option<String>, backoff: Option<u32> }, | ||
| ServerError { | ||
| code: u16, | ||
| text: String, | ||
| error_type: Option<String>, | ||
| backoff: Option<u32>, | ||
| response: RejectionStanza, // added in PR #1257 | ||
| }, | ||
| #[error("received unexpected IQ response type: {got:?}")] | ||
| UnexpectedResponseType { got: Option<String> }, | ||
| #[error("internal channel closed unexpectedly")] | ||
|
|
@@ -577,6 +612,8 @@ pub enum IqError { | |
|
|
||
| Added in PR #1100: `wacore::request::IqError` gained public `is_timeout()` (`true` only for `Timeout`) and `is_transport_unavailable()` (`true` for `NotConnected`, `Disconnected`, and `InternalChannelClosed`) methods, each an exhaustive match so a future variant has to be classified rather than silently defaulting to `false`. `whatsapp_rust::request::IqError` (the crate-level type shown above, with the extra `Socket`/`EncryptSend`/`ClientState`/`EncodeError`/`ParseError` variants) makes the same judgement internally but does not expose it publicly — go through [`ErrorChainExt`](#error-chain-recovery) instead, which handles both types. | ||
|
|
||
| Added in PR #1257: `ServerError` carries `response: RejectionStanza` — see [`RejectionStanza`](#rejectionstanza) above and the [migration note](#from-pr-1257-servererror-carries-the-rejection-stanza) below. Matching with `..` is unaffected by this field. A match that already names all four former fields without `..` needs `..` added (or `response` bound too) — Rust rejects a struct pattern missing a field with `E0027`. Constructing the variant by hand (mainly test fixtures) needs updating the same way. | ||
|
|
||
| ## Migration guide | ||
|
|
||
| ### From `anyhow::Error` | ||
|
|
@@ -701,3 +738,68 @@ match client.download(downloadable).await { | |
| ``` | ||
|
|
||
| Messages are unchanged either way — `http_status()` is purely additive, recovering a fact that was already in the text but previously reachable only by parsing it. | ||
|
|
||
| ### From PR #1257: `ServerError` carries the rejection stanza | ||
|
|
||
| `IqError::ServerError` gained a `response: RejectionStanza` field carrying the `type="error"` stanza verbatim, alongside the four fields it already parsed off it. Matching with `..` is unaffected — this is the common case, and nearly every match in the codebase already used it: | ||
|
|
||
| ```rust | ||
| // Still works unchanged | ||
| match err { | ||
| IqError::ServerError { code, text, .. } => { /* ... */ } | ||
| // ... | ||
| } | ||
| ``` | ||
|
|
||
| If you matched all four former fields by name without `..`, the pattern now fails to compile with `E0027` ("pattern does not mention field `response`") — add `..`, or bind `response` too: | ||
|
|
||
| ```rust | ||
| // Before — breaks with E0027 once `response` is added | ||
| IqError::ServerError { code, text, error_type, backoff } => { /* ... */ } | ||
|
|
||
| // After | ||
| IqError::ServerError { code, text, error_type, backoff, .. } => { /* ... */ } | ||
| ``` | ||
|
|
||
| Constructing the variant by hand — mainly test fixtures — now needs the stanza it was rejected with: | ||
|
|
||
| ```rust | ||
| // Before | ||
| IqError::ServerError { | ||
| code, | ||
| text, | ||
| error_type, | ||
| backoff, | ||
| } | ||
|
|
||
| // After — pass the response the rejection came with | ||
| IqError::ServerError { | ||
| code, | ||
| text, | ||
| error_type, | ||
| backoff, | ||
| response: response.into(), // response: Arc<OwnedNodeRef>, RejectionStanza converts via `.into()` | ||
| } | ||
| ``` | ||
|
|
||
| A fixture with no real wire response can build one directly, but `OwnedNodeRef::new` expects node bytes with the format byte already stripped, not the raw output of `marshal` — the same [`unpack`](/advanced/binary-protocol#the-format-byte) step the receive path runs ahead of every `OwnedNodeRef::new` call: | ||
|
|
||
| ```rust | ||
| let node = wacore_binary::builder::NodeBuilder::new("iq") | ||
| .attr("type", "error") | ||
| .children([/* ... */]) | ||
| .build(); | ||
| let packed = wacore_binary::marshal::marshal(&node).expect("marshals"); | ||
| let node_bytes = wacore_binary::util::unpack(&packed).expect("unpacks").into_owned(); | ||
| let response: Arc<OwnedNodeRef> = Arc::new(OwnedNodeRef::new(node_bytes).expect("decodes")); | ||
| ``` | ||
|
|
||
| `From<wacore::request::IqError> for whatsapp_rust::request::IqError` is also removed — a bare `From` conversion has no response available to attach, which is exactly the material this change stops discarding. Replace it with `IqError::from_response`: | ||
|
|
||
| ```rust | ||
| // Before | ||
| let err: IqError = wacore_err.into(); | ||
|
|
||
| // After — pass the response the classification failure was read from | ||
| let err = IqError::from_response(wacore_err, &response); | ||
| ``` | ||
Uh oh!
There was an error while loading. Please reload this page.