feat(client): WebSocket connect, call, id routing (#4) #22
No reviewers
Labels
No labels
area:assertions
area:cli
area:client
area:harness
area:meta
area:reporting
area:runner
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/ws-rpc-test!22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/4-client-connect-call"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #4. Stacked on #21.
Summary
First slice of
RpcClient: connection, request/response, JSON-RPC error mapping. Notifications are observed by the reader task but discarded for now — ticket #5 adds the bounded buffer, #6 addssubscribe/wait_for.Architecture
RpcClient { inner: Arc<Inner> }is cheaply cloneable. All public methods take&selfso cloned handles can be used concurrently from different tasks. Resolves spec-review §1.Innerholds anmpsc::UnboundedSender<Message>for the writer task, aMutex<HashMap<u64, oneshot::Sender<...>>>for pending requests, anAtomicU64id counter, and the default per-call timeout.write_loop: drains the mpsc into the WebSocket sink.read_loop: parses frames, dispatches id-bearing messages to the matching pending oneshot, drops everything else (notifications come back in #5).Connect
RpcClient::connect(url, token)wrapstokio_tungstenite::connect_asyncin a 10 s timeout.token: Option<&str>is sent asAuthorization: Bearer <token>via the upgrade headers.TestError::Connection.Call
call(method, params)uses the default 30 s timeout;call_timeout(method, params, duration)overrides it.AtomicU64.Value::Nullparams is rewritten to{}per spec §4.1.Err(TestError::RpcError { code, message }). Resolves spec-review §6 —callreturnsOk(Value)only on JSON-RPC success.Err(TestError::Timeout { event: "call:<method>", duration }). The pending entry is cleaned up on timeout.Checklist (from issue #4)
connect(url, token)with bearer auth + 10 s connect timeout&selfinterior mutability viaArc<Inner>(resolves spec review §1)call/call_timeoutwith auto-incrementing idcall_timeoutTestError::RpcError(resolves spec review §6)Value::Nullparams rewritten to{}(spec §4.1)TestError::Timeout { event: "call:<method>", .. }Test plan (7 new tests in
src/client.rs)spawn_echo_server— in-processtokio_tungstenite::accept_asyncfixture handlingping,fail,slowcall_returns_result_on_successcall_with_null_params_sends_empty_objectcall_returns_rpc_error_on_failure— assertsTestError::RpcError { code: -32601, .. }call_timeout_actually_times_out— assertsTestError::Timeoutwith"call:slow"eventids_increment_per_call— three sequential calls, each gets the right echo back (proves id routing)cloned_client_shares_connection— clones use the same underlying connectionconnect_to_invalid_url_returns_connection_errorjust qagreen locally — 27/27 unit tests passNotes for the reviewer
Defaultimpl forRpcClientexists only so the prelude re-export from #1 (pub use crate::client::RpcClient) keeps compiling. The default client's channels are detached so any call returnsErr. Real clients always come fromconnect.tokio_tungstenite::tungstenite::Message::Textin 0.26 takes aUtf8Bytes;String::into()works for the conversion.Message::Close, transport error, or end-of-stream.Review — WebSocket connect, call, id routing (#4)
Minor :
Ordering::SeqCstsur le compteur d'IDSeqCstimpose un ordre total global entre toutes les opérations atomiques du programme — largement plus fort que nécessaire ici.Relaxedsuffit pour un compteur monotone dont la seule contrainte est que chaque appel obtienne une valeur distincte.Minor : channel sortant non borné
mpsc::unbounded_channeln'a pas de backpressure. Pour des scénarios de test avec du parallélisme borné, c'est acceptable. Vaut la peine d'être documenté pour les utilisateurs qui construiraient des tests à débit élevé.Déconnexion du write loop et waiters en attente
Quand
write_loopse termine (connexion fermée), lesoneshot::Senderrestent dans laPendingMapjusqu'à l'expiration de leurs timeouts individuels. La déconnexion provoque donc une cascade deTimeoutplutôt qu'uneConnectionerror immédiate. Comportement attendu mais mériterait une note dans les docs ou un TODO.Ce qui est bien
Arc<Inner>pour clone sans coût — design correct.pendingmap au timeout — évite les fuites mémoire.{}conforme à la spec JSON-RPC et bien testé.Defaultimpl retourne un client détaché avec un doc-comment clair.fail,slow— bonne couverture.Commentaires sans bloquant. Deux points mineurs :
SeqCst→Relaxedsur le compteur d'ID, et documenter le comportement cascade-Timeout en cas de déconnexion.