Skip to content

feat(mex): typed mex operations from the whatspec IR, drop hand-maintained mex_ids - #728

Merged
jlucaso1 merged 1 commit into
mainfrom
feat/mex-typed-operations
Jun 5, 2026
Merged

feat(mex): typed mex operations from the whatspec IR, drop hand-maintained mex_ids#728
jlucaso1 merged 1 commit into
mainfrom
feat/mex-typed-operations

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Replaces the hand-maintained wacore::iq::mex_ids doc-id registry with the whatspec-generated typed operations, vendored verbatim as wacore::iq::mex_operations. One module per op, each with NAME / DOC_ID / OPERATION_KIND consts plus typed Variables / Response. Refresh by re-copying whatspec's generated/mex/operations.rs.

Fixes a latent bug for free

get_metadata and list_subscribed were sending persisted-query ids the current WA Web bundle no longer ships. The ops were renamed upstream (to WAWebMexFetchNewsletterJobQuery and WAWebMexFetchAllNewslettersMetadataJobQuery); the vendored file carries the current ids, so these stop silently failing.

Typed input, domain-parsed output

The generated Variables are a clean typed input and replace the previous json! objects. The response stays parsed by the existing domain parsers over data: Value: the generated Response mirror is a heuristic structural copy (no enums, some string fields typed as numbers, no flatten/coerce), so it is deliberately not used as a deserialize target. NewsletterMetadata and friends stay the domain layer.

Ergonomics + fewer allocations

A mex_request! macro pulls NAME/DOC_ID from a generated module, so the op is named once:

client.mex().query(mex_request!(fetch_newsletter {
    input: Some(fetch_newsletter::Input {
        key: Some(jid.to_string()), r#type: Some("JID".into()), view_role: Some("GUEST".into()),
    }),
    fetch_viewer_metadata: Some(true),
    ..Default::default()
})).await?;

MexRequest<V> / MexQuerySpec<V> now hold the typed variables and serialize them straight to the wire in build_iq, dropping the intermediate serde_json::Value tree. Construction is infallible, so callsites lose the ?.

The one loosely-typed op

update_group_property's update is a one-of object the generator's heuristic flattened to String. It now uses a hand-typed GroupPropertyUpdate enum (#[serde(rename_all = "snake_case")]) so that path is typed too, with a unit test locking the wire shape. No serde_json::Value remains on the mex request path.

Verification

cargo build, cargo clippy --all-targets -- -D warnings, and the full test suite (excluding e2e) all pass. The serialized request payloads are wire-equivalent to the previous json! ones (skip_serializing_if = "Option::is_none" drops the unset fields). This supersedes the closed #727 (build-time generation crate) in favor of vendoring the typed operations directly.

@coderabbitai

coderabbitai Bot commented Jun 5, 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: d760615e-65e5-44c1-89d1-ba88f2151c09

📥 Commits

Reviewing files that changed from the base of the PR and between c73d30a and 9ae4f36.

📒 Files selected for processing (9)
  • src/features/community.rs
  • src/features/groups.rs
  • src/features/mex.rs
  • src/features/newsletter.rs
  • wacore/src/iq/mex.rs
  • wacore/src/iq/mex_ids.rs
  • wacore/src/iq/mex_operations.rs
  • wacore/src/iq/mod.rs
  • wacore/src/iq/newsletter.rs
💤 Files with no reviewable changes (1)
  • wacore/src/iq/mex_ids.rs

📝 Walkthrough

Summary by CodeRabbit

  • Refactor
    • Rewrote internal GraphQL request/mutation plumbing to use typed variables and a centralized operations registry.
    • Reduced runtime serialization and improved payload construction for community, groups, and newsletter flows.
    • Updated related module comments and tests.
    • No public APIs or user-facing behavior changed; improvements increase reliability and maintainability.

Walkthrough

Migrate MEX request construction to a generic, typed API: add MexRequest and mex_request! macro, pre-serialize MexQuerySpec payloads, replace mex_ids with mex_operations, and update community, newsletter, and groups call sites to use typed operations and payload structs.

Changes

MEX API type-safety and macro-based request construction

Layer / File(s) Summary
Core MEX request types and macro
src/features/mex.rs
MexRequest becomes generic MexRequest<V> with new(...); mex_request! macro added to build requests from generated operation modules; Mex::query/mutate accept MexRequest<V> over V: Serialize.
Generic IQ spec for typed variables
wacore/src/iq/mex.rs
MexQuerySpec stores a pre-serialized payload: Vec<u8> and MexQuerySpec::new is generic over V: Serialize; build_iq writes the stored payload directly; tests updated.
Replace mex_ids registry with mex_operations module
wacore/src/iq/mod.rs, wacore/src/iq/newsletter.rs
Public iq export changes from mex_ids to mex_operations; docs/comments updated to reference the generated operation modules.
Community subgroup queries with typed operations
src/features/community.rs
get_subgroups and get_subgroup_participant_counts now call mex_request!(fetch_all_subgroups { ... }) and mex_request!(query_subgroup_participant_count { ... }) using typed Inputs; imports adjusted.
Newsletter CRUD operations with macro-based requests
src/features/newsletter.rs
All newsletter operations (list_subscribed, get_metadata, create, join, leave, update, get_metadata_by_invite) now use mex_request! with typed Input/Updates structs instead of JSON+document IDs.
Group property updates with typed payload structures
src/features/groups.rs
Add GroupPropertyUpdate, LimitSharingUpdate, UpdateGroupPropertyVars (serde-serialized); update set_member_link_mode, set_member_share_history_mode, set_limit_sharing to use typed variants; mex_update_group_property accepts the enum and builds a typed request; unit test verifies serialization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: replacing hand-maintained mex_ids with typed operations from whatspec IR.
Description check ✅ Passed The description thoroughly explains the refactor, typed operations approach, and fixes a latent bug in persisted-query IDs.
Linked Issues check ✅ Passed PR successfully delivers the core objective from #727: typed MEX operations with typed Variables, proper serialization, and a build-time generation approach, though with vendoring instead of internal crate.
Out of Scope Changes check ✅ Passed All changes directly support the typed operations migration. No out-of-scope modifications detected; changes are confined to mex infrastructure and consumers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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/mex-typed-operations

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.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 2,925 2,925 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 8,446 8,446 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 31,317 31,317 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 13,827 13,827 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 49,485 49,485 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 55,001 55,001 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 1,679 1,679 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 4,393 4,393 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 113,203 113,205 -0.0%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,731 1,656,618 +0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 651,823 651,786 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 875,808 875,752 +0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,083,097 2,083,679 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 749,059 749,066 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,326,079 1,329,822 -0.3%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,375,048 4,392,402 -0.4%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 509,578 517,652 -1.6%
binary_benchmark::marshal_group::bench_marshal_allocating 45,395 45,395 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 45,445 45,445 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 66,348 66,348 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 43,506 43,506 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 45,501 45,501 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 4,936 4,936 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 4,967 4,967 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 6,738 6,738 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 528,539 528,539 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 528,152 528,152 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 529,398 529,398 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 5,417,742 5,417,742 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 5,362,091 5,362,091 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 13,276,430 13,276,430 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 1,850 1,850 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 29,217 29,217 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 618 618 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 672,890 672,890 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 3,736 3,736 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 3,840 3,840 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 48,274 48,274 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 3,866 3,866 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 48,335 48,335 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 5,206 5,206 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 66,659 66,659 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 310,312 310,312 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 8,282 8,282 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 254 254 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 91 91 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 292 292 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 137 137 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 317 317 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 145 145 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 27,425 27,425 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 10,725 10,725 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 4,140,177 4,145,248 -0.1%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 100,131 100,133 -0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 4,264,189 4,264,189 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 100,399 100,399 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 210,262 210,262 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 496,908 496,921 -0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 508,246 510,275 -0.4%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,978,104 11,978,277 -0.0%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 2,466,138 2,466,138 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 4,931,452 4,904,352 +0.6%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,043,397 2,043,397 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 37,414 37,404 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 3,617,967 3,617,967 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 230,638 230,648 -0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 9,980,959 9,980,959 +0.0%
No significant changes detected.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c73d30ad28

ℹ️ 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".

Comment thread wacore/src/iq/mex.rs Outdated

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

Actionable comments posted: 1

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

Inline comments:
In `@wacore/src/iq/mex.rs`:
- Around line 162-165: The current use of
serde_json::to_vec(&payload).unwrap_or_default() silently drops serialization
errors into an empty payload; change this to fail fast by using expect() on
serde_json::to_vec(&payload) with a clear panic message referencing the payload
and context (e.g., "failed to serialize mex payload in mex.rs for payload:
{:?}") so payload_bytes is either valid JSON bytes or the process panics loudly;
update the assignment around payload_bytes and any related comments to reflect
the new fail-fast behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f622ab4b-146c-426c-8ac5-2ac9aa31283c

📥 Commits

Reviewing files that changed from the base of the PR and between 8d35b04 and c73d30a.

📒 Files selected for processing (9)
  • src/features/community.rs
  • src/features/groups.rs
  • src/features/mex.rs
  • src/features/newsletter.rs
  • wacore/src/iq/mex.rs
  • wacore/src/iq/mex_ids.rs
  • wacore/src/iq/mex_operations.rs
  • wacore/src/iq/mod.rs
  • wacore/src/iq/newsletter.rs
💤 Files with no reviewable changes (1)
  • wacore/src/iq/mex_ids.rs

Comment thread wacore/src/iq/mex.rs Outdated
…ained mex_ids

Replace the hand-maintained `wacore::iq::mex_ids` doc-id registry with the whatspec-generated typed operations, vendored verbatim as `wacore::iq::mex_operations` (one module per op: `NAME`/`DOC_ID`/`OPERATION_KIND` consts plus typed `Variables`/`Response`). Refresh by re-copying whatspec's `generated/mex/operations.rs`.

This fixes a latent bug for free: `get_metadata`/`list_subscribed` were sending persisted-query ids the current WA Web bundle no longer ships (the ops were renamed to `WAWebMexFetchNewsletterJobQuery` / `WAWebMexFetchAllNewslettersMetadataJobQuery`); the vendored file carries the current ids.

The response stays parsed by the existing domain parsers over `data: Value` (the generated `Response` mirror is a heuristic structural copy: it lacks enums, types some string fields as numbers, and doesn't flatten/coerce, so it is not a safe deserialize target). The generated `Variables`, in contrast, are a clean typed input.

Ergonomics: a `mex_request!` macro pulls `NAME`/`DOC_ID` from a generated module so the op is named once, and `MexRequest<V>` carries the typed variables. They are serialized once in `execute_request` into the wire payload (`MexQuerySpec` holds the bytes), so there is no intermediate `serde_json::Value` tree and a caller-side serialization error surfaces as `MexError::Json` instead of a malformed empty request. Callsites stay `?`-free (the serialization `?` lives in `execute_request`).

`update_group_property` is the one op the generated mirror types too loosely (its `update` is a one-of object the heuristic flattened to `String`); it now uses a hand-typed `GroupPropertyUpdate` enum (`#[serde(rename_all = "snake_case")]`) so that path is typed too, with a test locking the wire shape. No `serde_json::Value` remains on the mex request path.

Verified: build, clippy --all-targets -D warnings, and the full test suite (excluding e2e) all pass. The serialized request payloads are wire-equivalent to the previous `json!` ones (None fields are skipped).
@jlucaso1
jlucaso1 force-pushed the feat/mex-typed-operations branch from c73d30a to 9ae4f36 Compare June 5, 2026 14:51
@jlucaso1
jlucaso1 merged commit a0c7bc2 into main Jun 5, 2026
11 of 12 checks passed
@jlucaso1
jlucaso1 deleted the feat/mex-typed-operations branch June 5, 2026 15:06
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