Skip to content

feat: community support — full CRUD, subgroup management, queries - #392

Merged
jlucaso1 merged 3 commits into
mainfrom
feat/community-support
Mar 18, 2026
Merged

feat: community support — full CRUD, subgroup management, queries#392
jlucaso1 merged 3 commits into
mainfrom
feat/community-support

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Mar 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Full community (parent group) feature: create, deactivate, link/unlink subgroups, query metadata, join subgroups, fetch participants
  • Extends existing group protocol (w:g2 IQ namespace) with community-specific fields and 6 new IQ specs
  • MEX (GraphQL) queries for subgroup listing and participant counts
  • GroupMetadata extended with is_parent_group, parent_group_jid, is_default_sub_group, is_general_chat
  • New GroupType enum and group_type() helper for classifying groups within the community hierarchy

API

client.community().create(options).await?
client.community().deactivate(&jid).await?
client.community().link_subgroups(&parent, &[subgroup]).await?
client.community().unlink_subgroups(&parent, &[subgroup], remove_orphans).await?
client.community().get_subgroups(&parent).await?
client.community().get_subgroup_participant_counts(&parent).await?
client.community().query_linked_group(&parent, &subgroup).await?
client.community().join_subgroup(&parent, &subgroup).await?
client.community().get_linked_groups_participants(&parent).await?

New files

  • wacore/src/iq/community.rs — MEX doc ID constants
  • src/features/community.rsCommunity<'a> feature with 9 methods + types
  • tests/e2e/tests/community.rs — 10 E2E tests

Not covered (can be added later)

  • Dedicated community events (existing GroupUpdate with Link/Unlink actions covers this)
  • Subgroup suggestions (approve/reject/cancel)
  • Community owner transfer

Test plan

  • cargo fmt — clean
  • cargo clippy --all --tests — zero warnings
  • cargo test --all --exclude e2e-tests — all unit tests pass
  • cargo test -p e2e-tests --test community — 10/10 E2E tests pass
  • cargo test -p e2e-tests --test groups — 6/6 existing group tests pass (no regression)

Summary by CodeRabbit

  • New Features

    • Community management: create/deactivate communities, link/unlink subgroups, join subgroups, and manage group hierarchies.
    • Query subgroup info, participant counts, and retrieve participants across linked groups.
  • Improvements

    • Group metadata extended with parent/subgroup flags and related attributes to better represent community context.
  • Tests

    • Added comprehensive end-to-end tests covering community workflows, subgroup linking, joining, and participant scenarios.

… deactivate

Full community (parent group) feature implementation. Communities are WhatsApp's
hierarchical group structure: a parent group containing linked subgroups.

### API

```rust
// Creation & deletion
client.community().create(options).await?
client.community().deactivate(&jid).await?

// Subgroup management
client.community().link_subgroups(&parent, &[subgroup]).await?
client.community().unlink_subgroups(&parent, &[subgroup], remove_orphans).await?

// Queries (MEX GraphQL)
client.community().get_subgroups(&parent).await?
client.community().get_subgroup_participant_counts(&parent).await?

// Queries (IQ)
client.community().query_linked_group(&parent, &subgroup).await?
client.community().join_subgroup(&parent, &subgroup).await?
client.community().get_linked_groups_participants(&parent).await?
```

### New types
- `GroupType` — Default, Community, LinkedSubgroup, LinkedAnnouncementGroup, LinkedGeneralGroup
- `CreateCommunityOptions`, `CreateCommunityResult`
- `CommunitySubgroup` — subgroup metadata with participant count
- `LinkSubgroupsResult`, `UnlinkSubgroupsResult`
- `group_type()` helper to classify GroupMetadata

### Extended types
- `GroupMetadata` — added `is_parent_group`, `parent_group_jid`, `is_default_sub_group`, `is_general_chat`
- `GroupCreateOptions` — added `is_parent`, `closed`, `allow_non_admin_sub_group_creation`, `create_general_chat`
- `GroupInfoResponse` — community field parsing from w:g2 response nodes

### New IQ specs (wacore)
- `LinkSubgroupsIq` — link groups as subgroups
- `UnlinkSubgroupsIq` — unlink subgroups (with orphan member removal option)
- `DeleteCommunityIq` — deactivate a community
- `QueryLinkedGroupIq` — query subgroup metadata from parent
- `JoinLinkedGroupIq` — join a subgroup via parent community
- `GetLinkedGroupsParticipantsIq` — all participants across linked groups

### E2E tests (10/10 passing)
| Test | Covers |
|---|---|
| `test_community_create` | Create → verify is_parent_group + GroupType::Community |
| `test_community_create_with_general_chat` | Create with general chat → verify subgroup list |
| `test_community_get_subgroups` | Verify auto-created default announcement subgroup |
| `test_community_link_subgroup` | Create group → link → verify in subgroup list |
| `test_community_unlink_subgroup` | Link → unlink → verify removed from list |
| `test_community_deactivate` | Create → deactivate → verify deleted |
| `test_community_query_linked_group` | Query linked subgroup metadata |
| `test_community_join_subgroup` | Client B joins subgroup via community parent |
| `test_community_get_linked_groups_participants` | Fetch participants across linked groups |
| `test_community_subgroup_participant_counts` | MEX query for per-subgroup counts |
@coderabbitai

coderabbitai Bot commented Mar 18, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@jlucaso1 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 59 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ddb316d-725f-4d15-b554-7e4af9c09aaa

📥 Commits

Reviewing files that changed from the base of the PR and between 1de4928 and 537a752.

📒 Files selected for processing (3)
  • src/features/community.rs
  • src/features/groups.rs
  • tests/e2e/tests/community.rs
📝 Walkthrough

Walkthrough

Adds a new Community feature: a Community API module, community-aware IQ specs and mex docs, extensions to GroupMetadata and group IQ flows for parent/subgroup data, end-to-end tests, and public re-exports to expose community types and helpers.

Changes

Cohort / File(s) Summary
Community feature
src/features/community.rs
New module implementing Community<'_> API and helpers: create/deactivate communities, link/unlink subgroups, query/join linked groups, fetch subgroups and participants; public types: GroupType, CreateCommunityOptions/Result, CommunitySubgroup, Link/UnlinkSubgroupsResult; integrates IQ + MEX flows.
Feature exports
src/features/mod.rs, src/lib.rs
Added mod community and re-exported community public types and group_type to the crate public API.
Group metadata surface
src/features/groups.rs
Extended GroupMetadata with parent/subgroup fields (is_parent_group, parent_group_jid, is_default_sub_group, is_general_chat, allow_non_admin_sub_group_creation) and made from_response pub(crate); tests updated accordingly.
IQ: community docs
wacore/src/iq/community.rs
Added mex_docs constants (FETCH_ALL_SUBGROUPS, FETCH_SUBGROUP_SUGGESTIONS, FETCH_SUBGROUP_PARTICIPANT_COUNT) for GraphQL/MEX queries.
IQ: groups & community IQs
wacore/src/iq/groups.rs, wacore/src/iq/mod.rs
Extended GroupCreateOptions/GroupInfoResponse with community fields; added serialization/parsing for parent/default/general_chat fields; introduced Link/Unlink/Delete/Query/Join/GetLinkedGroupsParticipants IQ specs and related response types and parsing.
End-to-end tests
tests/e2e/tests/community.rs
New comprehensive e2e test suite exercising community creation, subgroup handling, linking/unlinking, deactivation, linked-group queries, joins across clients, and participant aggregation.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Feature as Community Feature
    participant IQ as IQ Layer
    participant MEX as MEX/GraphQL
    participant Server as WA Server

    Client->>Feature: community().create(options)
    activate Feature
    Feature->>IQ: Build GroupCreateIq / Delete/Link/Unlink Iq
    IQ->>Server: send IQ
    Server-->>IQ: IQ response
    IQ-->>Feature: parsed GroupInfoResponse / Link/Unlink result
    Feature->>MEX: MexRequest (mex_docs) for subgroup lists/participant counts
    MEX-->>Feature: MexResponse (nodes)
    Feature-->>Client: CreateCommunityResult / LinkSubgroupsResult / CommunitySubgroup list
    deactivate Feature
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #237: Introduced type-safe IQ specification infrastructure and MEX/GraphQL integration used by the community IQs and flows.
  • #304: Modifies GroupMetadata and its from_response conversion — overlaps fields and parsing changes added here.
  • #386: Refactors group parsing in wacore/src/iq/groups.rs, which this PR further extends for community/parent-group attributes.

Poem

🐰
I hopped through nodes and IQs today,
Linked little groups in tidy array,
Parents, children, threads combined —
Community gardens, well-aligned! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding comprehensive community support with CRUD operations, subgroup management, and query capabilities.

✏️ 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/community-support
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Mar 18, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 30s —— View job


Code Review: Community Support

  • Read changed files
  • Review IQ specs (wacore/src/iq/groups.rs, wacore/src/iq/community.rs)
  • Review feature implementation (src/features/community.rs)
  • Review module wiring (src/features/mod.rs, src/lib.rs)
  • Review E2E tests (tests/e2e/tests/community.rs)
  • Post findings

Overall this is a clean, well-structured addition. The IQ specs follow existing patterns, the feature handle mirrors Groups<'a>, and the E2E test coverage is solid. A few items worth addressing:

Issues

  1. Duplicated GroupInfoResponseGroupMetadata conversion (src/features/community.rs:339-363)

    group_info_to_metadata() is a field-by-field copy of GroupMetadata::from_response() in src/features/groups.rs:79-103. They do the exact same thing. If GroupInfoResponse gains a new field, only one of them will be updated.

    Consider reusing GroupMetadata::from_response() directly (it would need to be pub(crate) or pub) instead of maintaining a second copy:

    // community.rs
    pub async fn query_linked_group(...) -> Result<GroupMetadata, anyhow::Error> {
        let response = self.client.execute(QueryLinkedGroupIq::new(community_jid, subgroup_jid)).await?;
        Ok(GroupMetadata::from_response(response))
    }

    Fix this →

  2. allow_non_admin_sub_group_creation not propagated to GroupMetadata (src/features/groups.rs:78-103, src/features/community.rs:339-363)

    GroupInfoResponse parses allow_non_admin_sub_group_creation (line 636-638 in wacore/src/iq/groups.rs), but GroupMetadata has no corresponding field. Both from_response() and group_info_to_metadata() silently drop this value. If this field is relevant to consumers (e.g., for UI display), it should be added to GroupMetadata.

  3. Silent failures in MEX JSON parsing (src/features/community.rs:284)

    In get_subgroup_participant_counts, id_str defaults to "" via unwrap_or_default(), which then silently fails parse::<Jid>() and skips the entry. This is fine for robustness, but if a server-side schema change causes id to always be missing, you'd get an empty result with no indication of why. Consider logging a warning for entries that fail to parse.

  4. get_linked_groups_participants test doesn't assert creator presence (tests/e2e/tests/community.rs:418-437)

    The test fetches participants and logs whether the creator is present, but never assert!s it. The own_jid binding is unused for assertion. This makes the test always pass regardless of the result.

    Fix this →

Minor / Nits

  1. parse_subgroup_node is_general parameter is never true (src/features/community.rs:365-405)

    Both call sites pass is_general: false — the actual general-chat detection relies on is_general_from_props. The parameter could be removed to simplify the signature, unless you plan to use it from a different call site later.

  2. Unit tests for new IQ specs — The existing test suite in wacore/src/iq/groups.rs covers node building/parsing for all the original group IQ specs but doesn't include any for the 6 new community IQ specs (LinkSubgroupsIq, UnlinkSubgroupsIq, DeleteCommunityIq, QueryLinkedGroupIq, JoinLinkedGroupIq, GetLinkedGroupsParticipantsIq). Adding build_iq/parse_response round-trip tests would catch serialization issues without needing E2E infra.

What looks good

  • IQ wire formats match whatsmeow/Baileys patterns with proper XML doc comments
  • GroupType enum + group_type() helper is a clean abstraction for the community hierarchy
  • CreateCommunityOptions wrapping GroupCreateOptions keeps community creation separate while reusing the group creation IQ path
  • Link/unlink responses properly separate successful and failed groups
  • E2E tests cover the full lifecycle: create → link → query → join → unlink → deactivate
  • Module wiring and re-exports are clean

@github-actions

github-actions Bot commented Mar 18, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfeat/community-support
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkInstructionsBenchmark Result
instructions
(Result Δ%)
Upper Boundary
instructions
(Limit %)
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled()📈 view plot
🚷 view threshold
6,197.00
(-7.69%)Baseline: 6,713.06
7,048.72
(87.92%)
binary_benchmark::child_iteration_group::bench_get_children_by_tag📈 view plot
🚷 view threshold
524,304.00
(-37.03%)Baseline: 832,662.98
874,296.12
(59.97%)
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled()📈 view plot
🚷 view threshold
20,868.00
(-8.71%)Baseline: 22,859.02
24,001.98
(86.94%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
98,202.00
(-22.54%)Baseline: 126,777.84
133,116.73
(73.77%)
binary_benchmark::marshal_group::bench_marshal_auto_allocating📈 view plot
🚷 view threshold
98,230.00
(-16.57%)Baseline: 117,733.12
123,619.78
(79.46%)
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating📈 view plot
🚷 view threshold
532,948.00
(-0.19%)Baseline: 533,964.04
560,662.24
(95.06%)
binary_benchmark::marshal_group::bench_marshal_auto_long_string📈 view plot
🚷 view threshold
15,870.00
(-8.02%)Baseline: 17,254.05
18,116.75
(87.60%)
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating📈 view plot
🚷 view threshold
14,715,195.00
(-13.28%)Baseline: 16,968,493.83
17,816,918.52
(82.59%)
binary_benchmark::marshal_group::bench_marshal_exact_allocating📈 view plot
🚷 view threshold
118,358.00
(-31.38%)Baseline: 172,479.39
181,103.35
(65.35%)
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating📈 view plot
🚷 view threshold
534,378.00
(-0.19%)Baseline: 535,377.94
562,146.83
(95.06%)
binary_benchmark::marshal_group::bench_marshal_exact_long_string📈 view plot
🚷 view threshold
17,919.00
(-7.19%)Baseline: 19,307.11
20,272.47
(88.39%)
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating📈 view plot
🚷 view threshold
28,066,335.00
(-32.72%)Baseline: 41,715,672.22
43,801,455.83
(64.08%)
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating📈 view plot
🚷 view threshold
533,387.00
(-0.19%)Baseline: 534,403.04
561,123.19
(95.06%)
binary_benchmark::marshal_group::bench_marshal_long_string📈 view plot
🚷 view threshold
15,843.00
(-11.10%)Baseline: 17,821.83
18,712.92
(84.66%)
binary_benchmark::marshal_group::bench_marshal_many_children_allocating📈 view plot
🚷 view threshold
14,716,621.00
(-13.28%)Baseline: 16,969,382.30
17,817,851.42
(82.59%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
107,945.00
(-18.17%)Baseline: 131,908.79
138,504.23
(77.94%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer📈 view plot
🚷 view threshold
98,302.00
(-16.56%)Baseline: 117,805.12
123,695.38
(79.47%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
90,974.00
(-7.71%)Baseline: 98,574.29
103,503.00
(87.90%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,378.00
(-5.01%)Baseline: 7,766.98
8,155.33
(90.47%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled()📈 view plot
🚷 view threshold
91,005.00
(-3.12%)Baseline: 93,932.94
98,629.58
(92.27%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,401.00
(+0.64%)Baseline: 7,353.97
7,721.67
(95.85%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled()📈 view plot
🚷 view threshold
106,790.00
(-2.67%)Baseline: 109,717.94
115,203.83
(92.70%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,913.00
(+0.53%)Baseline: 8,865.97
9,309.27
(95.74%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
41,989.00
(-10.93%)Baseline: 47,139.57
49,496.55
(84.83%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
2,717.00
(-6.19%)Baseline: 2,896.38
3,041.20
(89.34%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
556,092.00
(+3.58%)Baseline: 536,887.73
563,732.11
(98.64%)
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
771.00
(-0.43%)Baseline: 774.34
813.06
(94.83%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,686,895.00
(-0.09%)Baseline: 27,712,571.28
29,098,199.84
(95.15%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,540,322.00
(-0.17%)Baseline: 5,549,600.97
5,827,081.02
(95.08%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
178,094.00
(+0.03%)Baseline: 178,044.65
186,946.88
(95.26%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
178,905.00
(+0.03%)Baseline: 178,855.97
187,798.77
(95.26%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,315,282.00
(+0.17%)Baseline: 17,285,621.97
18,149,903.07
(95.40%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
295,884.00
(+0.01%)Baseline: 295,847.21
310,639.57
(95.25%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,694,207.00
(+0.77%)Baseline: 12,597,410.21
13,227,280.72
(95.97%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
715,609.00
(-0.01%)Baseline: 715,703.74
751,488.93
(95.23%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
41,823.00
(+0.03%)Baseline: 41,812.13
43,902.74
(95.26%)
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction()📈 view plot
🚷 view threshold
15,561,842.00
(+0.00%)Baseline: 15,561,717.62
16,339,803.51
(95.24%)
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages()📈 view plot
🚷 view threshold
5,504,859.00
(-0.11%)Baseline: 5,510,826.83
5,786,368.17
(95.13%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
956,774.00
(-0.21%)Baseline: 958,819.90
1,006,760.89
(95.03%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,822,723.00
(-0.01%)Baseline: 2,822,871.39
2,964,014.96
(95.23%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,444,364.00
(-1.07%)Baseline: 3,481,671.25
3,655,754.81
(94.22%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
124,248,655.00
(-0.92%)Baseline: 125,400,338.46
131,670,355.39
(94.36%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
11,812.00
(+0.12%)Baseline: 11,798.22
12,388.14
(95.35%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,825.00
(+0.04%)Baseline: 3,823.37
4,014.54
(95.28%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
87,720.00
(-0.26%)Baseline: 87,947.01
92,344.36
(94.99%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
79,754.00
(-0.30%)Baseline: 79,994.97
83,994.72
(94.95%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
51,011.00
(-0.02%)Baseline: 51,020.65
53,571.69
(95.22%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,762.00
(+0.26%)Baseline: 5,747.13
6,034.48
(95.48%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,122.00
(+0.18%)Baseline: 2,118.13
2,224.04
(95.41%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,920.00
(+0.02%)Baseline: 21,915.77
23,011.56
(95.26%)
🐰 View full continuous benchmarking report in Bencher

1. Remove duplicated group_info_to_metadata(), use GroupMetadata::from_response() (now pub(crate))
2. Add allow_non_admin_sub_group_creation to GroupMetadata
3. Add warn! logging for unparseable MEX entries
4. Assert creator presence in get_linked_groups_participants test
5. Remove unused is_general parameter from parse_subgroup_node
6. Add 12 unit tests for community IQ specs (build + parse round-trips)

Also fixes:
- GetLinkedGroupsParticipantsIq parser handles participants nested in <group> nodes
- get_linked_groups_participants test links a subgroup first so there are participants to query

@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: 5

🧹 Nitpick comments (1)
src/features/community.rs (1)

339-363: Deduplicate the GroupInfoResponse -> GroupMetadata mapping.

This helper is the same field copy as GroupMetadata::from_response in src/features/groups.rs. A shared From<GroupInfoResponse> impl would keep future metadata additions from drifting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/community.rs` around lines 339 - 363, The mapping in
group_info_to_metadata duplicates GroupMetadata::from_response; replace this
with a single From<GroupInfoResponse> implementation used across the codebase:
implement impl From<GroupInfoResponse> for GroupMetadata (moving the
field-by-field copies from group_info_to_metadata / GroupMetadata::from_response
into that impl), then remove or refactor the group_info_to_metadata function to
construct GroupMetadata via GroupInfoResponse::into (or .into()) so all
conversions use the shared From<GroupInfoResponse> implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/community.rs`:
- Around line 7-14: CreateCommunityOptions::description is accepted by the
public API but never applied in create(), so callers see a blank description;
update the create() implementation in community.rs to either include the
provided CreateCommunityOptions::description in the group creation payload
(e.g., map it into the GroupCreateIq / GroupCreateOptions sent to the backend)
or, if the protocol cannot set description during creation, perform a
post-create mutation to set the description (or return an explicit unsupported
error instead of silently ignoring it). Locate the create() function and the
types CreateCommunityOptions, GroupCreateIq, and GroupCreateOptions to add the
description mapping or the follow-up update/error path.

In `@tests/e2e/tests/community.rs`:
- Around line 477-481: The test currently only checks presence of the subgroup
in counts using counts.iter().any(|(jid, _)| *jid == group.gid); change it to
locate the tuple for group.gid (e.g., using counts.iter().find or filter) and
assert that the associated count value is >= 1; update the assertion to fail if
no matching tuple is found or if the found tuple's second element is less than 1
so the test verifies the returned participant count, not just presence.
- Around line 431-437: The test currently only logs whether the creator JID
(own_jid) is present in participants but doesn't assert it, so empty or
misparsed participant lists pass; update the test in
tests/e2e/tests/community.rs to assert the participant results are non-empty and
that participants.iter().any(|p| p.jid == own_jid || p.phone_number.as_ref() ==
Some(&own_jid)) is true (use assert! with a clear message), keeping the existing
info! line for logging.
- Around line 74-78: The test currently only asserts a default announcement
subgroup via subgroups.iter().any(|s| s.is_default_sub_group) but never verifies
the general-chat was created; update the test to also assert that the subgroups
collection contains the general-chat subgroup (e.g., check
subgroups.iter().any(|s| s.name == "general-chat" or another identifying field)
so create_general_chat is actually exercised), locating the assertion near the
existing use of subgroups and the create_general_chat call.

In `@wacore/src/iq/groups.rs`:
- Around line 1759-1762: The parse_response in parse_response(&self, response:
&Node) only calls collect_children(container, "participant") so it misses
participants nested inside <group> wrappers; update parse_response (and/or add a
helper) to first find the required_child("linked_groups_participants"), then
collect participant nodes both directly and within any <group> children (e.g.
iterate container children: if tag == "participant" parse with
GroupParticipantResponse, if tag == "group" descend into that node and collect
its "participant" children), or implement a small recursive collect that accepts
"participant" and handles "group" containers, then return collect_children
results accordingly so community().get_linked_groups_participants() receives
participants whether nested or direct.

---

Nitpick comments:
In `@src/features/community.rs`:
- Around line 339-363: The mapping in group_info_to_metadata duplicates
GroupMetadata::from_response; replace this with a single From<GroupInfoResponse>
implementation used across the codebase: implement impl From<GroupInfoResponse>
for GroupMetadata (moving the field-by-field copies from group_info_to_metadata
/ GroupMetadata::from_response into that impl), then remove or refactor the
group_info_to_metadata function to construct GroupMetadata via
GroupInfoResponse::into (or .into()) so all conversions use the shared
From<GroupInfoResponse> implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 395e20b7-9ed5-4dee-b0a0-287c9383ea97

📥 Commits

Reviewing files that changed from the base of the PR and between f21c52d and d2382ff.

📒 Files selected for processing (8)
  • src/features/community.rs
  • src/features/groups.rs
  • src/features/mod.rs
  • src/lib.rs
  • tests/e2e/tests/community.rs
  • wacore/src/iq/community.rs
  • wacore/src/iq/groups.rs
  • wacore/src/iq/mod.rs

Comment thread src/features/community.rs Outdated
Comment thread tests/e2e/tests/community.rs
Comment thread tests/e2e/tests/community.rs Outdated
Comment thread tests/e2e/tests/community.rs
Comment thread wacore/src/iq/groups.rs

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

♻️ Duplicate comments (5)
tests/e2e/tests/community.rs (3)

479-483: ⚠️ Potential issue | 🟡 Minor

Validate the returned participant count, not just subgroup presence.

Line 481 only checks that the subgroup exists in the result, not that its count is meaningful.

Suggested patch
-    assert!(
-        counts.iter().any(|(jid, _)| *jid == group.gid),
-        "linked subgroup should appear in participant counts"
-    );
+    let linked_group_count = counts
+        .iter()
+        .find_map(|(jid, count)| (*jid == group.gid).then_some(*count));
+    assert!(
+        linked_group_count.is_some_and(|count| count >= 1),
+        "linked subgroup should report at least the creator"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/tests/community.rs` around lines 479 - 483, The test currently only
asserts the subgroup id appears in counts; update the assertion to also validate
the participant count is >= 1 by locating the tuple for group.gid in counts (use
counts.iter().find or counts.iter().any with a predicate that checks both *jid
== group.gid and count >= 1) and assert that the found count meets >= 1
(reference symbols: counts, group.gid). Ensure the failure message describes a
missing or zero participant count for the linked subgroup.

74-78: ⚠️ Potential issue | 🟡 Minor

Assert create_general_chat behavior explicitly.

Line 76 verifies only the default subgroup. This test can still pass even if general-chat creation regresses.

Suggested patch
     assert!(
         subgroups.iter().any(|s| s.is_default_sub_group),
         "should have a default announcement subgroup"
     );
+    assert!(
+        subgroups.iter().any(|s| s.is_general_chat),
+        "should have a general chat subgroup"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/tests/community.rs` around lines 74 - 78, The test currently only
asserts a default subgroup exists (checking subgroups.iter().any(|s|
s.is_default_sub_group)); explicitly assert the expected "general-chat" was
created by adding an assertion that a subgroup with the general chat
identifier/name exists and/or that the function/operation create_general_chat
succeeded (e.g., check for s.name == "general-chat" or s.is_general_chat) so
regressions in create_general_chat will fail the test; locate this in
tests/e2e/tests/community.rs near the existing subgroups assertions and add a
precise assertion referencing the subgroup name or flag.

425-439: ⚠️ Potential issue | 🟡 Minor

Strengthen participant assertion to include creator identity.

Line 436 only checks non-empty results; it does not prove the expected participant set includes the test account.

Suggested patch
     let participants = client
         .client
         .community()
         .get_linked_groups_participants(&community.gid)
         .await?;
+
+    let own_jid = client
+        .client
+        .get_pn()
+        .await
+        .expect("client should have PN")
+        .to_non_ad();
+    let own_lid = client
+        .client
+        .get_lid()
+        .await
+        .expect("client should have LID")
+        .to_non_ad();
 
     assert!(
         !participants.is_empty(),
         "should return at least the creator as a participant across linked groups"
     );
+    assert!(
+        participants.iter().any(|p| {
+            p.jid == own_jid || p.jid == own_lid || p.phone_number.as_ref() == Some(&own_jid)
+        }),
+        "linked_groups_participants should include the creator"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/tests/community.rs` around lines 425 - 439, The test currently only
asserts that participants is non-empty; update it to assert that the expected
creator/test account appears in the returned participants from
client.client.community().get_linked_groups_participants(&community.gid). Locate
the test variables that hold the creator's identity (e.g., the test account id
or community.creator) and add an assertion that participants contains that
identifier (for example by checking any participant.uid == expected_uid or
comparing actor ids), so the test verifies the creator is returned across linked
groups.
src/features/community.rs (1)

38-41: ⚠️ Potential issue | 🟠 Major

CreateCommunityOptions::description is currently a no-op.

Line 40 exposes description, but create() (Line 123-130) never applies it, so callers silently lose user input.

Suggested patch
-use crate::features::groups::GroupMetadata;
+use crate::features::groups::{GroupDescription, GroupMetadata};
@@
     pub async fn create(
         &self,
         options: CreateCommunityOptions,
     ) -> Result<CreateCommunityResult, anyhow::Error> {
+        let CreateCommunityOptions {
+            name,
+            description,
+            closed,
+            allow_non_admin_sub_group_creation,
+            create_general_chat,
+        } = options;
+
+        let description = description
+            .as_deref()
+            .map(GroupDescription::new)
+            .transpose()?;
+
         let create_options = GroupCreateOptions {
-            subject: options.name,
+            subject: name,
             is_parent: true,
-            closed: options.closed,
-            allow_non_admin_sub_group_creation: options.allow_non_admin_sub_group_creation,
-            create_general_chat: options.create_general_chat,
+            closed,
+            allow_non_admin_sub_group_creation,
+            create_general_chat,
             ..Default::default()
         };
@@
         let gid = self
             .client
             .execute(GroupCreateIq::new(create_options))
             .await?;
+
+        if let Some(description) = description {
+            self.client
+                .groups()
+                .set_description(&gid, Some(description), None)
+                .await?;
+        }
 
         Ok(CreateCommunityResult { gid })
     }

Also applies to: 119-138

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/community.rs` around lines 38 - 41, The CreateCommunityOptions
struct exposes a description field but the create() function never applies it,
so user-provided descriptions are dropped; update the create() implementation to
read CreateCommunityOptions::description and set the corresponding field on the
new Community (or include it in the DB insert/update) when constructing/saving
the community object (refer to CreateCommunityOptions::description and the
create() function where the Community is built/saved) so the description is
persisted.
wacore/src/iq/groups.rs (1)

1759-1775: ⚠️ Potential issue | 🟡 Minor

Collect both direct and nested participants in one pass.

Line 1764 returns early, so <participant> entries nested under <group> (Line 1770+) are skipped if any direct participant exists.

Suggested patch
     fn parse_response(&self, response: &Node) -> Result<Self::Response> {
         let container = required_child(response, "linked_groups_participants")?;
 
-        // Participants may be direct children or nested inside <group> nodes.
-        let direct = collect_children::<GroupParticipantResponse>(container, "participant")?;
-        if !direct.is_empty() {
-            return Ok(direct);
-        }
-
-        // Nested: <linked_groups_participants><group><participant/></group></linked_groups_participants>
-        let mut all = Vec::new();
+        // Participants may be direct children and/or nested inside <group> nodes.
+        let mut all = collect_children::<GroupParticipantResponse>(container, "participant")?;
         for group_node in container.get_children_by_tag("group") {
             let participants =
                 collect_children::<GroupParticipantResponse>(group_node, "participant")?;
             all.extend(participants);
         }
         Ok(all)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wacore/src/iq/groups.rs` around lines 1759 - 1775, The parse_response
function currently returns early when direct participants are found, skipping
nested <participant> entries; change it to always collect both direct and nested
participants by initializing a combined Vec (e.g., let mut all =
collect_children::<GroupParticipantResponse>(container, "participant")?;), then
iterate container.get_children_by_tag("group") and extend all with
collect_children::<GroupParticipantResponse>(group_node, "participant")?;
finally return Ok(all) instead of returning early from the direct branch; update
references to container, direct, all, group_node, and collect_children
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/features/community.rs`:
- Around line 38-41: The CreateCommunityOptions struct exposes a description
field but the create() function never applies it, so user-provided descriptions
are dropped; update the create() implementation to read
CreateCommunityOptions::description and set the corresponding field on the new
Community (or include it in the DB insert/update) when constructing/saving the
community object (refer to CreateCommunityOptions::description and the create()
function where the Community is built/saved) so the description is persisted.

In `@tests/e2e/tests/community.rs`:
- Around line 479-483: The test currently only asserts the subgroup id appears
in counts; update the assertion to also validate the participant count is >= 1
by locating the tuple for group.gid in counts (use counts.iter().find or
counts.iter().any with a predicate that checks both *jid == group.gid and count
>= 1) and assert that the found count meets >= 1 (reference symbols: counts,
group.gid). Ensure the failure message describes a missing or zero participant
count for the linked subgroup.
- Around line 74-78: The test currently only asserts a default subgroup exists
(checking subgroups.iter().any(|s| s.is_default_sub_group)); explicitly assert
the expected "general-chat" was created by adding an assertion that a subgroup
with the general chat identifier/name exists and/or that the function/operation
create_general_chat succeeded (e.g., check for s.name == "general-chat" or
s.is_general_chat) so regressions in create_general_chat will fail the test;
locate this in tests/e2e/tests/community.rs near the existing subgroups
assertions and add a precise assertion referencing the subgroup name or flag.
- Around line 425-439: The test currently only asserts that participants is
non-empty; update it to assert that the expected creator/test account appears in
the returned participants from
client.client.community().get_linked_groups_participants(&community.gid). Locate
the test variables that hold the creator's identity (e.g., the test account id
or community.creator) and add an assertion that participants contains that
identifier (for example by checking any participant.uid == expected_uid or
comparing actor ids), so the test verifies the creator is returned across linked
groups.

In `@wacore/src/iq/groups.rs`:
- Around line 1759-1775: The parse_response function currently returns early
when direct participants are found, skipping nested <participant> entries;
change it to always collect both direct and nested participants by initializing
a combined Vec (e.g., let mut all =
collect_children::<GroupParticipantResponse>(container, "participant")?;), then
iterate container.get_children_by_tag("group") and extend all with
collect_children::<GroupParticipantResponse>(group_node, "participant")?;
finally return Ok(all) instead of returning early from the direct branch; update
references to container, direct, all, group_node, and collect_children
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c1615abe-c7b3-4ca0-9206-68727f7e1ab6

📥 Commits

Reviewing files that changed from the base of the PR and between d2382ff and 1de4928.

📒 Files selected for processing (4)
  • src/features/community.rs
  • src/features/groups.rs
  • tests/e2e/tests/community.rs
  • wacore/src/iq/groups.rs

1. CreateCommunityOptions::description now applied via post-create set_description IQ
2. Participant counts test asserts count >= 1, not just presence
3. Linked groups participants test asserts creator JID is present
4. General chat test verifies is_general_chat subgroup exists
5. parse_response nested <group> handling — already fixed (verified)
6. Converted GroupMetadata::from_response() to From<GroupInfoResponse> trait impl
@jlucaso1
jlucaso1 merged commit 8aa68e3 into main Mar 18, 2026
8 checks passed
@jlucaso1
jlucaso1 deleted the feat/community-support branch March 18, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant