Skip to content

feat(usync): surface verified business name (verified_name) - #741

Merged
jlucaso1 merged 2 commits into
mainfrom
feat/usync-verified-name
Jun 8, 2026
Merged

feat(usync): surface verified business name (verified_name)#741
jlucaso1 merged 2 commits into
mainfrom
feat/usync-verified-name

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

What

usync already queries <business><verified_name/></business>, but the response was thrown away: UserInfo and IsOnWhatsAppResult only exposed is_business: bool. WA Web (WAWebUsyncBusiness businessParser) parses and displays the verified name, so the most useful part of the business query never reached consumers.

Changes

  • VerifiedName::try_from_node now decodes the certificate protobuf carried as the node's content bytes. The usync <verified_name> shape has no name/serial attrs; the name lives only inside VerifiedNameCertificate.Details.verified_name. The attr/<name> forms still take precedence when present, so the business-notification path is unchanged.
  • verified_name: Option<VerifiedName> added to UserInfo and IsOnWhatsAppResult, parsed from <business><verified_name> in both usync parse paths (parse_user_common_fields and IsOnWhatsAppSpec::parse_response).
  • VerifiedName re-exported from the contacts feature.

Verification

  • cargo clippy --all-targets -- -D warnings clean
  • cargo test -p wacore green (certificate decode + attr-precedence covered)

Resolves gap-analysis features-05. Breaking (adds a struct field), acceptable pre-1.0.

usync already queries <business><verified_name/></business> but the response was
discarded: UserInfo/IsOnWhatsAppResult only exposed is_business: bool. WA Web
(WAWebUsyncBusiness businessParser) parses and shows the verified name.

Extends VerifiedName::try_from_node to decode the certificate protobuf carried as
the node's content bytes (the usync shape has no name/serial attrs; the name lives
in VerifiedNameCertificate.Details.verified_name). Attr/child forms still win when
present (business-notification path unchanged). Adds verified_name: Option<VerifiedName>
to UserInfo and IsOnWhatsAppResult, parsed from <business><verified_name> in both
usync parse paths, and re-exports VerifiedName.

Verified: clippy --all-targets -- -D warnings clean; wacore tests green (cert-decode
and attr-precedence covered).
@coderabbitai

coderabbitai Bot commented Jun 8, 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: e6768e34-a7bb-45bf-9a35-a6b15b414b2c

📥 Commits

Reviewing files that changed from the base of the PR and between 166bb16 and dec8a1c.

📒 Files selected for processing (2)
  • src/lib.rs
  • wacore/src/iq/usync.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Support for retrieving and displaying verified business names for WhatsApp contacts.
    • Decoding of business verification certificates to extract verified name and validation details.
    • Verified business name info is now included in contact queries and user info responses, making authenticated business verification visible to users.

Walkthrough

Adds protobuf certificate decoding for VerifiedName, exposes VerifiedName through feature and crate re-exports, and propagates parsed verified_name into usync user result structs (IsOnWhatsAppResult and UserInfo).

Changes

VerifiedName Certificate Parsing and Usync Propagation

Layer / File(s) Summary
Certificate decoding in VerifiedName parser
wacore/src/stanza/business.rs
Imports prost::Message; makes name/serial/issuer mutable in VerifiedName::try_from_node; decodes certificate bytes to VerifiedNameCertificate and fills missing fields from decoded details; includes tests for decoding and attribute precedence.
VerifiedName type export pipeline
src/features/contacts.rs, src/features/mod.rs, src/lib.rs
Re-exports VerifiedName from wacore::stanza::business in contacts.rs, adds it to features public re-exports, and exposes it from the crate root.
Usync user result integration
wacore/src/iq/usync.rs
Imports VerifiedName; adds parse_verified_name helper; extends internal ParsedUserFields with verified_name; adds verified_name: Option<VerifiedName> to IsOnWhatsAppResult and UserInfo; wires verified_name population in parse_response paths; adds unit test for parse behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: surfacing the verified business name from usync responses, which aligns with all file modifications.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the motivation, implementation approach, and verification steps taken.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% 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 feat/usync-verified-name

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 8, 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,213 113,075 +0.1%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,621 1,656,621 +0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 651,789 651,786 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 875,773 875,833 -0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,083,478 2,083,509 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 749,067 749,069 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,329,855 1,326,069 +0.3%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,373,780 4,373,873 -0.0%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 519,868 518,998 +0.2%
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,143,540 4,137,463 +0.1%
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,399 510,188 +0.0%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,978,219 11,977,191 +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,853,852 4,937,852 -1.7%
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,648 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.

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 `@src/features/mod.rs`:
- Line 31: The public API is missing the VerifiedName re-export; update the
crate root's pub re-exports to include VerifiedName by adding it to the existing
pub use features::{...} list so consumers can access VerifiedName (i.e., ensure
the pub use that re-exports types from the features module includes VerifiedName
alongside the other items).
🪄 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: ff95f729-fbeb-4e23-b2ab-86d1d9f0a736

📥 Commits

Reviewing files that changed from the base of the PR and between d840086 and 166bb16.

📒 Files selected for processing (4)
  • src/features/contacts.rs
  • src/features/mod.rs
  • wacore/src/iq/usync.rs
  • wacore/src/stanza/business.rs

Comment thread src/features/mod.rs

@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: 166bb16fa7

ℹ️ 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/usync.rs Outdated
user_node
.get_optional_child("business")
.and_then(|business| business.get_optional_child("verified_name"))
.and_then(|vn| VerifiedName::try_from_node(vn).ok())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip empty verified-name nodes

When usync returns a per-user <business><verified_name><error .../></verified_name></business> or an empty marker for a business account without a verified-name cert, try_from_node still returns Ok(VerifiedName { name: None, serial: None, certificate: None }) because it never treats error/empty nodes as absent. Both public result types then expose verified_name: Some(...), so callers using is_some() to decide whether a verified business name exists get a false positive; this should mirror the status/picture parsers and return None unless the node actually carries a usable attr/name/certificate and has no error child.

Useful? React with 👍 / 👎.

… verified_name

Review follow-up:
- VerifiedName is now re-exported in src/lib.rs (not only features/mod.rs), so the
  type returned by UserInfo/IsOnWhatsAppResult is actually reachable.
- parse_verified_name returns None when <verified_name> has an <error> child or is
  an empty marker (no name, no cert), mirroring the status/picture parsers, so
  verified_name.is_some() means a real verified name was returned (no false positive
  for business accounts without a cert).
@jlucaso1
jlucaso1 merged commit f4bcd0f into main Jun 8, 2026
11 checks passed
@jlucaso1
jlucaso1 deleted the feat/usync-verified-name branch June 8, 2026 01:38
@mintlify

mintlify Bot commented Jun 8, 2026

Copy link
Copy Markdown

Docs PR opened: oxidezap/whatsapp-rust-docs#276

Documented the new verified_name field on IsOnWhatsAppResult and UserInfo, exposing decoded verified business names from contact lookups.

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