Skip to content

api: mark lib-constructed response/result structs #[non_exhaustive] for 1.0 - #794

Merged
jlucaso1 merged 1 commit into
mainfrom
api/non-exhaustive-response-structs
Jun 9, 2026
Merged

api: mark lib-constructed response/result structs #[non_exhaustive] for 1.0#794
jlucaso1 merged 1 commit into
mainfrom
api/non-exhaustive-response-structs

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

What

Adds #[non_exhaustive] to the public response/result structs the library returns to consumers. Before this, only IsOnWhatsAppResult carried the attribute (the convention exists but was applied to exactly one of ~23 such types), so adding a field to any of the others is a breaking change for downstream crates (Veloz, ESP32 firmware, whatsapp-ui). This lands the attribute before 1.0, while it is still cheap.

Scope and why it is safe

#[non_exhaustive] only restricts construction and exhaustive matching from other crates. I limited the change to structs constructed exclusively within their defining crate (verified: zero struct-literal constructions outside the defining crate), so the attribute is purely additive: field reads keep working, and only external struct-literal construction and exhaustive matching of types nobody outside the lib builds are blocked.

  • wacore IQ responses: UserInfo, LidQueryResponse, BusinessProfile, BusinessHours, BusinessHoursConfig, BusinessCategory, GroupInfoResponse, GroupParticipantResponse, ParticipantChangeResponse, GroupParticipatingResponse
  • whatsapp-rust returns: UploadResponse, SendResult, CreateGroupResult, CreateCommunityResult, CommunitySubgroup, LinkSubgroupsResult, UnlinkSubgroupsResult

Deliberately excluded (separate follow-ups)

  • Event payload structs (Receipt, ConnectFailure, ...) plus MessageSource and DeviceListResponse: defined in wacore but constructed in the whatsapp-rust crate (cross-crate), so #[non_exhaustive] would break the build. They need wacore-side constructors first.
  • SendOptions: users construct it via struct literal, so it needs a builder alongside #[non_exhaustive] (an API-design change), not a one-line attribute.

Verification

Whole-workspace --all-targets build passes (this compiles every crate's tests, so any cross-crate exhaustive-match break would have surfaced), and the exact CI clippy command (cargo clippy --all-targets -- -D warnings) passes. No behavior change.

…or 1.0

These structs are returned to consumers and only IsOnWhatsAppResult carried
#[non_exhaustive] (the convention exists but was applied to exactly one of ~23).
Adding a field to any of the others is a breaking change for downstream
(Veloz, ESP32 firmware, whatsapp-ui), so this lands the attribute before 1.0.

Scope is limited to structs constructed exclusively within their defining crate,
so #[non_exhaustive] (which only restricts other crates) is purely additive: it
keeps field reads working and blocks only external struct-literal construction
and exhaustive matching of types nobody outside the lib builds.

- wacore IQ responses: UserInfo, LidQueryResponse, BusinessProfile, BusinessHours,
  BusinessHoursConfig, BusinessCategory, GroupInfoResponse, GroupParticipantResponse,
  ParticipantChangeResponse, GroupParticipatingResponse
- whatsapp-rust returns: UploadResponse, SendResult, CreateGroupResult,
  CreateCommunityResult, CommunitySubgroup, LinkSubgroupsResult, UnlinkSubgroupsResult

Deliberately excluded:
- Event payload structs (Receipt, ConnectFailure, ...) and MessageSource /
  DeviceListResponse: defined in wacore but constructed in the whatsapp-rust crate
  (cross-crate), so #[non_exhaustive] would break the build; they need wacore
  constructors first (separate follow-up).
- SendOptions: users construct it via struct literal, so it needs a builder
  alongside #[non_exhaustive] (an API-design change, separate follow-up).

Verified: whole-workspace --all-targets build and the CI clippy command both pass.
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • Breaking Changes
    • Updated public API types for improved forward compatibility. Code using exhaustive pattern matching on these types may require updates to handle the changes.

Walkthrough

This PR marks fifteen public struct types across the codebase with #[non_exhaustive], spanning community and group feature APIs, core message/upload responses, business info types, group query responses, and user sync types. No functional logic or field definitions change—only compile-time exhaustiveness constraints are altered.

Changes

API forward compatibility via non_exhaustive

Layer / File(s) Summary
Community and group feature result types
src/features/community.rs, src/features/groups.rs
CreateCommunityResult, CommunitySubgroup, LinkSubgroupsResult, UnlinkSubgroupsResult, and CreateGroupResult are marked #[non_exhaustive] to allow safe field additions in future community and group operation results.
Message sending and upload response types
src/send.rs, src/upload.rs
SendResult and UploadResponse are marked #[non_exhaustive], preventing exhaustive pattern matching on message send and file upload completion payloads.
Business profile and category IQ types
wacore/src/iq/business.rs
BusinessProfile, BusinessHours, BusinessHoursConfig, and BusinessCategory are marked #[non_exhaustive] to support future additions to business metadata responses.
Group information and participant IQ response types
wacore/src/iq/groups.rs
GroupParticipantResponse, GroupInfoResponse, GroupParticipatingResponse, and ParticipantChangeResponse are marked #[non_exhaustive], enabling safe field additions in group query and membership change responses.
User sync and LID query IQ types
wacore/src/iq/usync.rs
UserInfo and LidQueryResponse are marked #[non_exhaustive], allowing forward-compatible user identity and sync response payloads.

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly Related PRs

  • oxidezap/whatsapp-rust#457: Overlaps on #[non_exhaustive] application to the same community, group creation, and message sending types.
  • oxidezap/whatsapp-rust#741: Both PRs modify UserInfo in wacore/src/iq/usync.rs—this PR adds #[non_exhaustive] while the related PR adds/parses the verified_name field.
  • oxidezap/whatsapp-rust#624: Both PRs affect ParticipantChangeResponse in wacore/src/iq/groups.rs—this PR marks it non_exhaustive while the related PR modifies its response payload.

Suggested Labels

api-design, breaking-change

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding #[non_exhaustive] to public response/result structs before the 1.0 release.
Description check ✅ Passed The description is directly related to the changeset, providing clear rationale, scope, and safety verification for the #[non_exhaustive] additions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 api/non-exhaustive-response-structs

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 9, 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,927 2,927 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 8,448 8,448 +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,487 49,487 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 55,003 55,003 +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,205 113,208 -0.0%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,627 1,656,733 -0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 644,511 644,472 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 868,503 868,512 -0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,076,249 2,076,231 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 741,847 741,849 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,319,485 1,323,128 -0.3%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,381,254 4,363,697 +0.4%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 510,572 512,124 -0.3%
binary_benchmark::marshal_group::bench_marshal_allocating 45,381 45,381 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 45,431 45,431 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 66,334 66,334 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 43,492 43,492 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 45,487 45,487 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 4,945 4,945 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 4,976 4,976 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 6,747 6,747 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 528,544 528,544 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 528,165 528,165 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 529,411 529,411 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 5,417,732 5,417,732 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 5,362,047 5,362,047 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 13,276,365 13,276,365 +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,291 8,291 +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,135,816 4,142,867 -0.2%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 100,133 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,921 496,921 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 510,487 505,496 +1.0%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,975,834 11,978,878 -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,907,592 4,921,562 -0.3%
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.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wacore/src/iq/usync.rs (1)

502-505: ⚠️ Potential issue | 🟠 Major

Add #[non_exhaustive] to DeviceListResponse (constructed in wacore)

DeviceListResponse is defined in wacore/src/iq/usync.rs and the response is built there (Ok(DeviceListResponse { ... })), but the struct itself has no #[non_exhaustive]. This breaks consistency with the other usync response types in the same module (which do have #[non_exhaustive]) and undermines the stated exclusion rationale—mark DeviceListResponse with #[non_exhaustive] to avoid downstream breakage before 1.0.

🤖 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 `@wacore/src/iq/usync.rs` around lines 502 - 505, DeviceListResponse lacks the
#[non_exhaustive] attribute unlike the other usync response types; add
#[non_exhaustive] immediately above the pub struct DeviceListResponse
declaration so future fields can be added without breaking downstream consumers
(the struct contains device_lists: Vec<UserDeviceList> and lid_mappings:
Vec<UsyncLidMapping>, and is constructed in this crate, so adding the attribute
here is safe and keeps consistency).
🤖 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.

Outside diff comments:
In `@wacore/src/iq/usync.rs`:
- Around line 502-505: DeviceListResponse lacks the #[non_exhaustive] attribute
unlike the other usync response types; add #[non_exhaustive] immediately above
the pub struct DeviceListResponse declaration so future fields can be added
without breaking downstream consumers (the struct contains device_lists:
Vec<UserDeviceList> and lid_mappings: Vec<UsyncLidMapping>, and is constructed
in this crate, so adding the attribute here is safe and keeps consistency).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 08400e48-2d1d-4cdf-a6a9-eb1374f28502

📥 Commits

Reviewing files that changed from the base of the PR and between b61b1df and 2a7ac82.

📒 Files selected for processing (7)
  • src/features/community.rs
  • src/features/groups.rs
  • src/send.rs
  • src/upload.rs
  • wacore/src/iq/business.rs
  • wacore/src/iq/groups.rs
  • wacore/src/iq/usync.rs

@jlucaso1
jlucaso1 merged commit 7411500 into main Jun 9, 2026
12 of 13 checks passed
@jlucaso1
jlucaso1 deleted the api/non-exhaustive-response-structs branch June 9, 2026 13:40
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