feat(rust-sdk): contacts parity — unblock + stats, typed responses & tests#228
Conversation
…s & tests The Rust ContactsApi was missing unblock() and stats() (both exist in the backend and the TS SDK), and returned untyped serde_json::Value. Add typed contact types mirroring sdk/typescript/src/types/contacts.ts (normalizing the backend cryptoId wire name to agent_id), give every method a typed return, add list/requests pagination params, and add 10 wiremock tests (previously zero contacts coverage). Claude-Session: https://claude.ai/code/session_01MhVGKa9mSYkgY3kFtddR87
|
@senamakel is attempting to deploy a commit to the Vezures Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds typed contact-graph structs to the Rust SDK, updates ChangesTyped Contacts API
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ContactsApi
participant HttpBackend
Client->>ContactsApi: list(Some(params))
ContactsApi->>ContactsApi: list_query(params) builds limit/offset
ContactsApi->>HttpBackend: GET /contacts?limit&offset
HttpBackend-->>ContactsApi: JSON payload
ContactsApi-->>Client: ContactsResponse (typed)
Related Issues: None specified. Related PRs: None specified. Suggested labels: rust-sdk, api, enhancement Suggested reviewers: None specified. Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a49fda73d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// Get the relationship status with `agent_id`. Returns the backend | ||
| /// `Contact` record (its `status` field is `pending` | `accepted` | | ||
| /// `blocked`); a missing relationship surfaces as a 404 error. | ||
| pub async fn status(&self, agent_id: &str) -> Result<Contact> { |
There was a problem hiding this comment.
Return the status endpoint's status shape
When /contacts/{id}/status returns the status response shape (agentId, status, optional direction) exposed by the TS SDK in sdk/typescript/src/types/contacts.ts:42-45 and used by the test mock in sdk/typescript/tests/codex-cli.test.ts:603-609, deserializing it as Contact silently drops agentId/direction and fills requester/addressee with empty defaults. Rust callers then cannot distinguish pending incoming vs outgoing (or read the returned peer), so the typed surface is lossy; add a ContactStatusResponse type and return it here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🧹 Nitpick comments (4)
sdk/rust/tests/api_contacts.rs (2)
91-120: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor: nested
contactfield andoutgoing[0].directionaren't asserted.
contacts_list_parses_and_paginatesdoesn't assert onres.contacts[0].contact, andcontacts_requests_parses_incoming_outgoingdoesn't assertoutgoing[0].direction. Not a correctness issue, just leaves part of the typed surface unexercised.Also applies to: 135-151
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/rust/tests/api_contacts.rs` around lines 91 - 120, The contacts API tests are not fully exercising the typed response surface: `contacts_list_parses_and_paginates` should also assert the nested `res.contacts[0].contact` fields, and `contacts_requests_parses_incoming_outgoing` should assert `outgoing[0].direction`. Update the assertions in `contacts_list_parses_and_paginates` and the incoming/outgoing parsing test so both the nested contact object and the outgoing direction field are validated.
16-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse a realistic
Contactresponse here —request/accept/blockall returnResult<Contact>, but these tests only exercise a{}body. Because everyContactfield defaults, they won’t catch typed-response regressions; assert at least one parsed field from a JSON payload instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/rust/tests/api_contacts.rs` around lines 16 - 24, The contact request test currently only validates an empty `{}` response, so it can miss typed-response regressions in the `client.contacts.request` flow. Update the relevant tests around `contacts_request_posts_to_other` (and the related `accept`/`block` cases if present) to use a realistic JSON `Contact` payload and assert at least one parsed field from the returned `Result<Contact>` instead of relying solely on defaults.sdk/rust/src/api/contacts.rs (1)
122-133: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
list_queryduplicatesContactListParams's own serialization logic.
ContactListParamsalready derivesSerializewithrename_all = "camelCase", butlist_querymanually re-extractslimit/offsetinto aVec<(String, String)>. If a new field is added toContactListParamslater, it's easy to forget to also add it here. Consider deriving the query pairs from the struct's ownSerializeimpl (e.g. viaserde_urlencodedor a small helper that pairs values), so the query stays in sync with the type definition automatically.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/rust/src/api/contacts.rs` around lines 122 - 133, The list_query helper is manually duplicating ContactListParams field handling, so updates to the struct can get out of sync. Refactor list_query to derive the query pairs from ContactListParams’ existing Serialize implementation instead of hand-pushing limit and offset, using a helper like serde_urlencoded or equivalent; keep the logic centered around list_query and ContactListParams so any new fields are automatically included.sdk/rust/src/types/contacts.rs (1)
57-62: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueConsider unsigned integer types for pagination/counts.
ContactListParams.limit/offsetandContactStats.contact_count/pending_incoming/pending_outgoingare typed asi64, but none of these values can be meaningfully negative. Usingu32/u64would make invalid states unrepresentable at the type level rather than relying on the backend to reject bad values.♻️ Example diff
pub struct ContactListParams { #[serde(default, skip_serializing_if = "Option::is_none")] - pub limit: Option<i64>, + pub limit: Option<u32>, #[serde(default, skip_serializing_if = "Option::is_none")] - pub offset: Option<i64>, + pub offset: Option<u32>, }Also applies to: 85-95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/rust/src/types/contacts.rs` around lines 57 - 62, The pagination and count fields in ContactListParams and ContactStats are using signed integers even though negative values are invalid. Update the relevant type definitions in contacts.rs so ContactListParams.limit and offset, plus ContactStats.contact_count, pending_incoming, and pending_outgoing, use unsigned integer types such as u32 or u64 as appropriate, and keep the serde attributes aligned so serialization still works correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@sdk/rust/src/api/contacts.rs`:
- Around line 122-133: The list_query helper is manually duplicating
ContactListParams field handling, so updates to the struct can get out of sync.
Refactor list_query to derive the query pairs from ContactListParams’ existing
Serialize implementation instead of hand-pushing limit and offset, using a
helper like serde_urlencoded or equivalent; keep the logic centered around
list_query and ContactListParams so any new fields are automatically included.
In `@sdk/rust/src/types/contacts.rs`:
- Around line 57-62: The pagination and count fields in ContactListParams and
ContactStats are using signed integers even though negative values are invalid.
Update the relevant type definitions in contacts.rs so ContactListParams.limit
and offset, plus ContactStats.contact_count, pending_incoming, and
pending_outgoing, use unsigned integer types such as u32 or u64 as appropriate,
and keep the serde attributes aligned so serialization still works correctly.
In `@sdk/rust/tests/api_contacts.rs`:
- Around line 91-120: The contacts API tests are not fully exercising the typed
response surface: `contacts_list_parses_and_paginates` should also assert the
nested `res.contacts[0].contact` fields, and
`contacts_requests_parses_incoming_outgoing` should assert
`outgoing[0].direction`. Update the assertions in
`contacts_list_parses_and_paginates` and the incoming/outgoing parsing test so
both the nested contact object and the outgoing direction field are validated.
- Around line 16-24: The contact request test currently only validates an empty
`{}` response, so it can miss typed-response regressions in the
`client.contacts.request` flow. Update the relevant tests around
`contacts_request_posts_to_other` (and the related `accept`/`block` cases if
present) to use a realistic JSON `Contact` payload and assert at least one
parsed field from the returned `Result<Contact>` instead of relying solely on
defaults.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9e6ffeab-8c1c-4757-84ff-5508dbd4a22a
📒 Files selected for processing (4)
sdk/rust/src/api/contacts.rssdk/rust/src/types/contacts.rssdk/rust/src/types/mod.rssdk/rust/tests/api_contacts.rs
…in wrapper The live backend (and the Rust SDK, #228) return 404 from GET /contacts/:id/status when no relationship exists yet. The TS SDK's ContactStatusResponse already models a "none" status, but contacts.status() rethrew the 404 — so any status-before-request caller (the wrapper's contact bootstrap) threw before it ever sent the request, which is why `pnpm cli:ts codex` never connected to a fresh owner. - ContactsApi.status(): map a 404 to { agentId, status: "none" } (honor the typed contract); rethrow other errors. Verified against staging. - harness-wrapper ensureContactNow + ensureOwnerContact: request-first (idempotent; auto-accepts a reverse request), then read status — never pre-check, so a fresh relationship's 404 can't abort the handshake. - Tests: new contacts.test.ts (404→none, passthrough, non-404 rethrow); mock relay models contacts.request idempotency (dedupe) since the wrapper now requests from both the outbound publisher and inbound receiver.
Why
The Rust SDK's
ContactsApihad drifted behind the backend and the TypeScript SDK (the source of truth):unblock()— the backend exposesPOST /contacts/:other/unblockand the TS SDK hascontacts.unblock(), but Rust could block and never unblock.stats()—GET /contacts/stats/contacts.stats()in TS, absent in Rust.serde_json::Value.An accepted contact relationship is the gate in front of direct messaging (the relay refuses DMs between non-contacts), so this surface matters for any Rust agent that wants to message.
What
contacts.unblock()(POST /contacts/:other/unblock) andcontacts.stats()(GET /contacts/stats).src/types/contacts.rsmirroringsdk/typescript/src/types/contacts.ts(Contact,ContactView,ContactsResponse,ContactRequestsResponse,ContactStats,ContactListParams), tolerant serde that normalizes the backendcryptoIdwire name toagent_id(matching the TS SDK's normalized surface).list/requestspagination params.tests/api_contacts.rs: 10 wiremock tests covering all 9 methods + typed-response deserialization (incl. thecryptoId → agent_idalias).Testing
cargo test— full suite green, including the 10 new contacts tests.cargo clippy --all-targets— clean.e2e-contactsflow (request/accept/block/unblock/stats/remove) passes end to end.https://claude.ai/code/session_01MhVGKa9mSYkgY3kFtddR87
Summary by CodeRabbit
New Features
Bug Fixes