Skip to content

refactor: replace stringly-typed APIs with enums across codebase - #440

Merged
jlucaso1 merged 8 commits into
mainfrom
refactor/type-safety-audit
Mar 26, 2026
Merged

refactor: replace stringly-typed APIs with enums across codebase#440
jlucaso1 merged 8 commits into
mainfrom
refactor/type-safety-audit

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Mar 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Full type-safety audit: replace raw String parameters with proper enums wherever the WhatsApp protocol defines a finite set of valid values. All changes verified against captured WhatsApp Web JS.

Changes

Area Old Type New Type WA Web Reference
Media host type String ("primary"/"fallback") HostType enum WAWeb/Media/Host.js
Business hours day String ("sun".."sat") DayOfWeek enum WAWeb/Business/ProfileTypes.js
Business hours mode String ("open_24h" etc.) BusinessHourMode enum WAWeb/Business/ProfileTypes.js
Newsletter msg type String ("text"/"media" etc.) NewsletterMessageType enum WAWeb/Newsletter/MsgParser.js
Dirty bits type DirtyType (missing variants) Added SyncdAppState, NewsletterMetadata WAWeb/Dirty/BitsConsts.js
Dirty bits client API (&str, Option<&str>) DirtyBit (typed struct)
PreKey fetch reason Option<String> Option<PreKeyFetchReason> enum WAWeb/Wam/EnumPrekeysFetchContext.js

Breaking changes

  • Client::clean_dirty_bits() now accepts DirtyBit instead of (&str, Option<&str>)
  • MediaConnHost.host_type and MediaConnHostExtended.host_type changed from String to HostType
  • BusinessHoursConfig.day_of_week / .mode changed from String to enums
  • NewsletterMessage.message_type changed from String to NewsletterMessageType
  • PreKeyFetchSpec.reason changed from Option<String> to Option<PreKeyFetchReason>

14 files changed, 216 additions, 67 deletions.

Test plan

  • All 29 affected tests pass (cargo test -p wacore --lib -- iq::mediaconn iq::dirty iq::prekeys iq::business)
  • cargo clippy --all --tests clean (0 warnings)

Summary by CodeRabbit

  • New Features

    • Added typed newsletter message kinds, business-hours day/mode, media host types, and message category values.
  • Improvements

    • Replaced many string-based flags with strongly-typed enums for cleaner parsing and safer defaults.
    • More robust handling for dirty notifications, pre-key fetch reasons, encryption message types, and delivery/receipt logic.
  • Bug Fixes

    • Safer timestamp parsing and fallbacks to avoid invalid-value errors.

Type-safety audit: replace raw String parameters with proper enums
where the protocol defines a finite set of valid values. All changes
verified against captured WhatsApp Web JS.

HostType enum (wacore/src/iq/mediaconn.rs):
- Replace host_type: String with HostType enum (Primary | Fallback)
- Verified against WAWeb/Media/Host.js: Object.freeze({ PRIMARY, FALLBACK })

DayOfWeek + BusinessHourMode enums (wacore/src/iq/business.rs):
- Replace day_of_week: String with DayOfWeek enum (sun..sat)
- Replace mode: String with BusinessHourMode enum (open_24h | specific_hours | appointment_only)
- Verified against WAWeb/Business/ProfileTypes.js

NewsletterMessageType enum (src/features/newsletter.rs):
- Replace message_type: String with NewsletterMessageType enum
- Variants: Text, Media, Reaction, Revoke, PollCreation, PollVote, Edit + Other fallback
- Verified against WAWeb/Newsletter/MsgParser.js

DirtyType expansion + Client API (wacore/src/iq/dirty.rs, src/client.rs, src/handlers/ib.rs):
- Add SyncdAppState and NewsletterMetadata variants to DirtyType
- Change Client::clean_dirty_bits to accept DirtyBit instead of raw strings
- Handler now uses enum pattern matching instead of string comparisons
- Verified against WAWeb/Dirty/BitsConsts.js

PreKeyFetchReason enum (wacore/src/iq/prekeys.rs):
- Replace reason: Option<String> with Option<PreKeyFetchReason>
- Variants: Identity, Retry + Other fallback
- Updated all callers in src/prekeys.rs, src/client/context_impl.rs, src/client/sessions.rs
@coderabbitai

coderabbitai Bot commented Mar 26, 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 8 minutes and 33 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e6bf653d-f899-438c-8f34-46be1a2ec8cc

📥 Commits

Reviewing files that changed from the base of the PR and between 4860842 and 9c1b132.

📒 Files selected for processing (5)
  • src/features/newsletter.rs
  • src/handlers/ib.rs
  • wacore/src/iq/dirty.rs
  • wacore/src/iq/prekeys.rs
  • wacore/src/types/user.rs
📝 Walkthrough

Walkthrough

This PR replaces multiple string-based parameters and fields with strongly-typed enums across the codebase (dirty bits, prekey reasons, newsletter message types, media host types, business hours, message categories, and encryption types). Call sites, parsing, IQ builders, and tests were updated to construct, compare, and serialize the new typed values.

Changes

Cohort / File(s) Summary
Dirty bits & handler
wacore/src/iq/dirty.rs, src/handlers/ib.rs, src/client.rs
Adds DirtyType variants (SyncdAppState, NewsletterMetadata); CleanDirtyBitsSpec::single now accepts DirtyBit (adds from_raw for string parsing). Handler parses type/timestamp into typed DirtyType/DirtyBit and calls Client::clean_dirty_bits(DirtyBit).
Prekey fetch reason
wacore/src/iq/prekeys.rs, src/prekeys.rs, src/client/context_impl.rs, src/client/sessions.rs
Adds PreKeyFetchReason enum (Identity, Retry, Other) and changes fetch_pre_keys signatures to accept Option<PreKeyFetchReason>; call sites now pass enum variants. Response parsing and spec construction updated accordingly.
Newsletter types & exports
src/features/newsletter.rs, src/features/mod.rs, src/lib.rs
Adds NewsletterMessageType enum and changes NewsletterMessage.message_type: StringNewsletterMessageType. Parser maps <message type="..."> to enum (default Text). Re-exports updated.
Media connection host typing
wacore/src/iq/mediaconn.rs, src/mediaconn.rs
Adds HostType enum (Primary, Fallback, Other) and replaces host_type: String with HostType in host structs and constructors; parsing/serialization and host selection/sorting updated. HostType re-export added.
Business hours enums
wacore/src/iq/business.rs, wacore/src/types/user.rs
Adds DayOfWeek and BusinessHourMode enums; BusinessHoursConfig fields switched from String to these enums. IQ parsing maps attributes to enums.
Message encryption & categories
src/message.rs, wacore/src/types/message.rs, wacore/src/messages.rs
Introduces EncType usage for <enc type="..."> parsing and routing (session vs sender-key); adds MessageCategory enum and changes MessageInfo.category: StringMessageCategory, updating parsing and checks.
Receipts, retry, PDO & stanza usage
src/receipt.rs, src/retry.rs, src/pdo.rs, wacore/src/stanza/receipt.rs, wacore/src/messages.rs
Replaces string "peer" handling with MessageCategory::Peer in delivery/receipt/retry logic and tests; initializes category values via MessageCategory::default() where applicable.
Tests & miscellaneous small API surface updates
wacore/src/iq/dirty.rs tests, wacore/src/iq/prekeys.rs tests, various small files
Unit tests updated to use new enums and constructors; minor re-exports added (e.g., HostType) and small builders/adapters adjusted to use typed enums (privacy addressing_mode, newsletter defaults, etc.).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through strings to find new names,
Enums in burrows, tidy little frames.
Dirty bits cleaned, prekeys neatly queued,
Newsletters typed, hosts primed and viewed.
Hooray — type-safe carrots for every claim!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and accurately summarizes the main objective: replacing string-based parameters with enums across the codebase for improved type safety.
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 refactor/type-safety-audit

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 26, 2026

Copy link
Copy Markdown

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


Code Review: Type-Safety Audit

  • Gather context and read changed files
  • Review enum definitions in wacore
  • Review usage sites in src/
  • Post findings

Overall this is a clean, well-structured refactor. The enum definitions are correct, the #[string_fallback] pattern handles unknown protocol values gracefully, and test coverage is thorough. A few observations:

Looks Good

  • Consistent StringEnum pattern: All new enums (HostType, DayOfWeek, BusinessHourMode, NewsletterMessageType, PreKeyFetchReason, DirtyType additions) follow the same #[string_fallback] Other(String) pattern for forward compatibility with unknown protocol values.
  • DirtyBit struct: Nice upgrade from the raw (&str, Option<&str>) tuple to a proper typed struct. The from_raw() fallback for parsing from protocol nodes keeps the migration clean.
  • HostType with #[string_default]: Good use of #[string_default] on Primary so missing type attributes default correctly, matching WA Web's mapParsedMediaConn behavior.
  • IB handler refactor (src/handlers/ib.rs): The matches!() macro for gating dirty types behind offline delivery is cleaner and less error-prone than string comparisons.
  • Test updates: All tests updated to use the new typed APIs, and new tests added for from_raw() and DirtyType::from() round-tripping.

Minor Suggestions

  1. NewsletterMessageType default (src/features/newsletter.rs:599): The fallback when the type attribute is missing uses NewsletterMessageType::Other(String::new()) — an empty-string Other. This is functionally fine but semantically odd. Consider whether NewsletterMessageType::Text would be a better default (if WA Web defaults to text for untyped messages), or add a dedicated Unknown variant. Low priority since missing type attrs are unlikely in practice.

  2. Duplicate BusinessHoursConfig (wacore/src/types/user.rs:64-68 vs wacore/src/iq/business.rs:91-100): There are now two BusinessHoursConfig structs — one in types/user.rs (updated to use the new enums via crate::iq::business::DayOfWeek) and one in iq/business.rs. The types/user.rs version has non-optional open_time/close_time (plain String) while the iq/business.rs version has Option<String>. This was pre-existing, but now that both reference the same enum types, it might be worth consolidating or at least adding a comment explaining why they differ. Not a blocker.

  3. dirty_type.clone() in IB handler (src/handlers/ib.rs:51-53): DirtyType is cloned twice — once for the DirtyBit and once for the matches! check inside the spawned task. Since DirtyType contains a String variant, consider moving the type check before constructing the bit, or restructuring to avoid the second clone. Negligible perf impact but would be slightly cleaner.

Verdict

This is a solid type-safety improvement. The breaking API changes are well-documented, all enums have fallback variants for forward compatibility, and the test coverage validates the changes. Approve.


@github-actions

github-actions Bot commented Mar 26, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchrefactor/type-safety-audit
Testbedubuntu-latest

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()Instructions
instructions x 1e3
📈 plot
🚷 threshold
🚨 alert (🔔)
47.13 x 1e3
(+10.20%)Baseline: 42.77 x 1e3
44.90 x 1e3
(104.96%)

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
(-6.05%)Baseline: 6,595.78
6,925.57
(89.48%)
binary_benchmark::child_iteration_group::bench_get_children_by_tag📈 view plot
🚷 view threshold
524,304.00
(-29.05%)Baseline: 738,962.73
775,910.87
(67.57%)
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled()📈 view plot
🚷 view threshold
20,868.00
(-6.21%)Baseline: 22,250.66
23,363.19
(89.32%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
98,202.00
(-18.36%)Baseline: 120,283.33
126,297.50
(77.75%)
binary_benchmark::marshal_group::bench_marshal_auto_allocating📈 view plot
🚷 view threshold
98,230.00
(-11.21%)Baseline: 110,629.34
116,160.80
(84.56%)
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating📈 view plot
🚷 view threshold
532,948.00
(-0.12%)Baseline: 533,593.96
560,273.66
(95.12%)
binary_benchmark::marshal_group::bench_marshal_auto_long_string📈 view plot
🚷 view threshold
15,870.00
(-5.25%)Baseline: 16,749.93
17,587.42
(90.23%)
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating📈 view plot
🚷 view threshold
14,715,201.00
(-8.87%)Baseline: 16,147,756.92
16,955,144.77
(86.79%)
binary_benchmark::marshal_group::bench_marshal_exact_allocating📈 view plot
🚷 view threshold
118,358.00
(-22.52%)Baseline: 152,766.30
160,404.61
(73.79%)
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating📈 view plot
🚷 view threshold
534,378.00
(-0.12%)Baseline: 535,013.72
561,764.41
(95.12%)
binary_benchmark::marshal_group::bench_marshal_exact_long_string📈 view plot
🚷 view threshold
17,919.00
(-4.69%)Baseline: 18,801.51
19,741.59
(90.77%)
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating📈 view plot
🚷 view threshold
28,066,347.00
(-23.62%)Baseline: 36,744,061.47
38,581,264.54
(72.75%)
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating📈 view plot
🚷 view threshold
533,387.00
(-0.12%)Baseline: 534,032.96
560,734.61
(95.12%)
binary_benchmark::marshal_group::bench_marshal_long_string📈 view plot
🚷 view threshold
15,843.00
(-8.05%)Baseline: 17,230.33
18,091.85
(87.57%)
binary_benchmark::marshal_group::bench_marshal_many_children_allocating📈 view plot
🚷 view threshold
14,716,627.00
(-8.87%)Baseline: 16,148,841.18
16,956,283.24
(86.79%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
107,945.00
(-14.64%)Baseline: 126,462.47
132,785.59
(81.29%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer📈 view plot
🚷 view threshold
98,302.00
(-11.20%)Baseline: 110,701.34
116,236.40
(84.57%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
90,974.00
(-5.80%)Baseline: 96,574.21
101,402.92
(89.72%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,378.00
(-3.74%)Baseline: 7,664.62
8,047.85
(91.68%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled()📈 view plot
🚷 view threshold
91,005.00
(-2.00%)Baseline: 92,866.47
97,509.79
(93.33%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,401.00
(+0.41%)Baseline: 7,371.10
7,739.65
(95.62%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled()📈 view plot
🚷 view threshold
106,790.00
(-1.71%)Baseline: 108,651.47
114,084.04
(93.61%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,913.00
(+0.34%)Baseline: 8,883.10
9,327.25
(95.56%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
41,989.00
(-8.66%)Baseline: 45,968.99
48,267.44
(86.99%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
2,717.00
(-4.85%)Baseline: 2,855.61
2,998.39
(90.62%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
556,092.00
(+2.74%)Baseline: 541,252.33
568,314.95
(97.85%)
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
771.00
(-0.33%)Baseline: 773.58
812.26
(94.92%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,809,020.00
(+0.38%)Baseline: 27,702,747.07
29,087,884.42
(95.60%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,544,446.00
(-0.07%)Baseline: 5,548,176.19
5,825,585.00
(95.17%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
175,119.00
(-1.44%)Baseline: 177,671.85
186,555.44
(93.87%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
175,768.00
(-1.51%)Baseline: 178,461.27
187,384.34
(93.80%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,347,079.00
(+0.39%)Baseline: 17,278,861.80
18,142,804.89
(95.61%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
298,353.00
(+0.59%)Baseline: 296,599.92
311,429.92
(95.80%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,588,992.00
(-0.06%)Baseline: 12,596,175.54
13,225,984.32
(95.18%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
719,597.00
(+0.42%)Baseline: 716,581.54
752,410.62
(95.64%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
🚨 view alert (🔔)
47,129.00
(+10.20%)Baseline: 42,765.36
44,903.63
(104.96%)

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,755.01
16,339,842.76
(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,371,271.00
(-2.18%)Baseline: 5,490,781.05
5,765,320.10
(93.17%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
311,806.00
(-64.07%)Baseline: 867,731.70
911,118.28
(34.22%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,830,547.00
(+0.20%)Baseline: 2,824,891.48
2,966,136.05
(95.43%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,452,844.00
(-0.59%)Baseline: 3,473,280.48
3,646,944.50
(94.68%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
126,189,218.00
(+0.68%)Baseline: 125,336,824.90
131,603,666.15
(95.89%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
12,002.00
(+1.67%)Baseline: 11,804.50
12,394.72
(96.83%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,930.00
(+2.72%)Baseline: 3,825.81
4,017.10
(97.83%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
87,073.00
(-0.87%)Baseline: 87,839.36
92,231.33
(94.41%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
79,022.00
(-1.08%)Baseline: 79,883.08
83,877.23
(94.21%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
50,375.00
(-1.17%)Baseline: 50,973.38
53,522.05
(94.12%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,956.00
(+3.50%)Baseline: 5,754.46
6,042.18
(98.57%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,227.00
(+4.99%)Baseline: 2,121.08
2,227.14
(99.99%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,920.00
(+0.01%)Baseline: 21,916.73
23,012.57
(95.25%)
🐰 View full continuous benchmarking report in Bencher

@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: 44f6ba5612

ℹ️ 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 src/handlers/ib.rs Outdated
Comment on lines +49 to +52
let ts = timestamp_str.and_then(|s| s.parse::<u64>().ok());
let bit = match ts {
Some(t) => DirtyBit::with_timestamp(dirty_type.clone(), t),
None => DirtyBit::new(dirty_type.clone()),

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 Do not drop unparseable dirty timestamps

Parsing timestamp with s.parse::<u64>().ok() silently converts malformed values into None, and the code then sends clean without a timestamp. In the malformed-input path (for example, unexpected server format or overflow), this can acknowledge a dirty bit at the wrong scope and risk state divergence instead of surfacing the protocol error. Please preserve the error path (or skip cleaning that bit) rather than silently stripping the timestamp.

Useful? React with 👍 / 👎.

@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 the current code and only fix it if needed.

Inline comments:
In `@src/handlers/ib.rs`:
- Around line 46-53: Remove the intermediate owned String allocation by parsing
the timestamp directly from the borrowed &str returned by
attrs.optional_string("timestamp"); replace the current timestamp_str mapping
and ts computation with a direct chain like
attrs.optional_string("timestamp").and_then(|s| s.parse::<u64>().ok()) to
produce ts, then continue to build DirtyBit using
DirtyType::from(dirty_type_str.as_ref()) and either
DirtyBit::with_timestamp(dirty_type.clone(), t) or
DirtyBit::new(dirty_type.clone()) as before; this eliminates the unnecessary
.to_string() allocation while keeping the same control flow.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7d34aa2-10f0-4a27-ba9b-c108f8a1a052

📥 Commits

Reviewing files that changed from the base of the PR and between 08dc6f4 and 44f6ba5.

📒 Files selected for processing (14)
  • src/client.rs
  • src/client/context_impl.rs
  • src/client/sessions.rs
  • src/features/mod.rs
  • src/features/newsletter.rs
  • src/handlers/ib.rs
  • src/lib.rs
  • src/mediaconn.rs
  • src/prekeys.rs
  • wacore/src/iq/business.rs
  • wacore/src/iq/dirty.rs
  • wacore/src/iq/mediaconn.rs
  • wacore/src/iq/prekeys.rs
  • wacore/src/types/user.rs

Comment thread src/handlers/ib.rs Outdated
- Move dirty type checks before DirtyBit construction to avoid cloning
- Default missing newsletter message type to Text instead of empty Other
- Remove self-explanatory doc comments on enum definitions
Parse timestamp directly from borrowed Cow<str> instead of allocating
an owned String first.
@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Mar 26, 2026

Copy link
Copy Markdown

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


Code Review: Type-Safety Audit

  • Gather context and read changed files
  • Review enum definitions in wacore
  • Review usage sites and IB handler
  • Post findings

Clean, well-structured refactor. The enum definitions are consistent, the #[string_fallback] pattern provides forward compatibility, and test coverage is solid. A few observations:

Looks Good

  • Consistent StringEnum pattern across all new enums (HostType, DayOfWeek, BusinessHourMode, NewsletterMessageType, PreKeyFetchReason, new DirtyType variants) — all use #[string_fallback] Other(String) for unknown protocol values.
  • DirtyBit struct is a nice upgrade from raw (&str, Option<&str>) tuples. The from_raw() constructor with proper error handling for invalid timestamps is good defensive coding.
  • HostType with #[string_default] on Primary correctly matches WA Web's mapParsedMediaConn behavior for missing type attributes.
  • IB handler (src/handlers/ib.rs): The matches!() macro for gating dirty types behind offline delivery is cleaner than string comparisons. The latest commit (48828c9) addressed the previous review's clone feedback — dirty_type is now only moved, not redundantly cloned.
  • Test coverage: Good round-trip tests, from_raw() tests, and DirtyType::from() variant tests.

Suggestions

  1. NewsletterMessageType default (src/features/newsletter.rs:599): The fallback for a missing type attribute defaults to NewsletterMessageType::Text. This seems like a reasonable choice (untyped messages are likely text), but worth confirming against WA Web's WAWeb/Newsletter/MsgParser.js. If the server always sends a type attribute, this is moot.

  2. Duplicate BusinessHoursConfig (wacore/src/types/user.rs:64-70 vs wacore/src/iq/business.rs:86-94): There are two BusinessHoursConfig structs — types/user.rs has non-optional open_time/close_time (String), while iq/business.rs has Option<String>. Both now reference the same enum types (DayOfWeek, BusinessHourMode), which is good, but the divergence in optionality is a bit confusing. Pre-existing issue, not introduced by this PR — just noting it as a potential follow-up consolidation.

  3. CleanDirtyBitsSpec::from_raw() is unused (wacore/src/iq/dirty.rs:61): The IB handler constructs DirtyBit directly via DirtyBit::new()/DirtyBit::with_timestamp() and silently drops unparseable timestamps via .parse::<u64>().ok(). Meanwhile from_raw() does proper error handling with map_err. Consider either:

    • Using from_raw() in the IB handler to get consistent error reporting, or
    • Documenting that from_raw() is intended for external callers.

    As the Codex review noted, silently converting malformed timestamps to None in the IB handler could mask protocol issues.

Verdict

Solid type-safety improvement. Breaking changes are well-documented, all enums have fallback variants, and the test suite validates the changes thoroughly. Approve


View job

…strings

- Use existing EncType::from_wire() in src/message.rs instead of matching
  against raw "pkmsg"/"msg"/"skmsg" strings (3 sites)
- Add MessageCategory enum (Peer | Empty | Other) replacing raw "peer"
  string comparisons across receipt.rs, retry.rs, stanza/receipt.rs (6 sites)
- Replace hardcoded .attr("addressing_mode", "lid") with
  AddressingMode::Lid.as_str() in message.rs and privacy.rs (5 sites)
- Add TODO comments for review follow-ups: newsletter default type,
  duplicate BusinessHoursConfig, from_raw() vs .ok() in IB handler
@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Mar 26, 2026

Copy link
Copy Markdown

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


Code Review: Type-Safety Audit

  • Gather context and read changed files
  • Review enum definitions in wacore
  • Review usage sites in src/
  • Review latest commits (addressing prior feedback)
  • Post findings

Thorough review of all 22 changed files across 4 commits. This is a clean, well-executed type-safety refactor. The code is correct, consistent, and the follow-up commits properly addressed earlier review feedback.

Looks Good

  • Consistent StringEnum derive pattern — All new enums (HostType, DayOfWeek, BusinessHourMode, NewsletterMessageType, PreKeyFetchReason, MessageCategory, new DirtyType variants) use #[string_fallback] Other(String) for forward-compatible unknown protocol values.
  • DirtyBit struct — Clean upgrade from (&str, Option<&str>) to a proper typed struct with new() / with_timestamp() constructors. The from_raw() method with proper error reporting is available for callers who want strict validation, and the TODO comment at line 61-62 documents the conscious trade-off in the IB handler.
  • HostType with #[string_default] on Primary (wacore/src/iq/mediaconn.rs:31) — Correctly matches WA Web's mapParsedMediaConn default behavior for missing type attributes.
  • IB handler (src/handlers/ib.rs) — The latest commits addressed the CodeRabbit feedback well: removed the intermediate .to_string() allocation (commit 48828c9), and dirty_type is now moved into DirtyBit without redundant clones. The matches!() check for offline-delivery gating is cleaner than string comparisons.
  • MessageCategory enum (wacore/src/types/message.rs:33-42) with #[string_default] on Empty — Good addition. Now receipt logic (src/receipt.rs:27, wacore/src/stanza/receipt.rs:31) uses info.category == MessageCategory::Peer instead of string comparisons. Much safer.
  • EncType enum (wacore/src/message_processing.rs) — Uses from_wire() returning Option<Self> rather than StringEnum since there's no meaningful fallback for unknown encryption types (a message with an unknown enc type can't be decrypted). Correct design choice.
  • PreKeyFetchSpec.reason (wacore/src/iq/prekeys.rs:156) — Clean pass-through via self.reason.as_ref().map(|r| r.as_str()) preserves the wire format while keeping the public API typed.
  • Test coverage — Comprehensive tests for DirtyType::from() round-tripping, from_raw() error handling, MediaConnHostExtended round-trips with typed HostType, and receipt logic using MessageCategory::Peer.

One Minor Note

Duplicate BusinessHoursConfig (wacore/src/types/user.rs:66-72 vs wacore/src/iq/business.rs:87-94) — The TODO comment at types/user.rs:64 acknowledges this. Both now use DayOfWeek/BusinessHourMode enums which is good, but they diverge on open_time/close_time optionality. Not a blocker — just a consolidation opportunity for a follow-up.

Verdict

Approve — Solid type-safety improvement. Breaking changes are well-documented, all enums have fallback variants for forward compatibility, and test coverage validates the changes thoroughly. No issues found.


View job

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

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/dirty.rs (1)

60-73: 🛠️ Refactor suggestion | 🟠 Major

Put raw parsing on DirtyBit and give it a typed error.

Client::clean_dirty_bits() now accepts a DirtyBit, but the only strict raw parser lives on CleanDirtyBitsSpec and returns anyhow::Error. That makes the src/handlers/ib.rs call site duplicate timestamp parsing, and it already diverged by silently dropping invalid timestamps. A DirtyBit::from_raw(...) -> Result<DirtyBit, DirtyBitParseError> would keep validation in one place and let CleanDirtyBitsSpec::from_raw(...) delegate to it.

As per coding guidelines, "Use thiserror for typed errors in Rust code" and "Use anyhow for multi-failure functions in Rust code".

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

In `@wacore/src/iq/dirty.rs` around lines 60 - 73, Introduce a strict raw parser
on DirtyBit by adding DirtyBit::from_raw(dirty_type: &str, timestamp:
Option<&str>) -> Result<DirtyBit, DirtyBitParseError> that parses the timestamp
and returns a typed error implemented with thiserror (DirtyBitParseError)
instead of using anyhow; move the timestamp parsing logic currently in
CleanDirtyBitsSpec::from_raw into that new DirtyBit::from_raw and have
CleanDirtyBitsSpec::from_raw delegate to DirtyBit::from_raw to construct its
bits, then update Client::clean_dirty_bits() call sites (including
src/handlers/ib.rs) to use DirtyBit::from_raw and propagate/report the typed
error instead of duplicating parsing or silently dropping invalid timestamps via
.ok().
🤖 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/newsletter.rs`:
- Around line 595-601: Remove the TODO and add a unit test that locks the
fallback behavior: ensure when a message node is missing the "type" attribute
the parser returns NewsletterMessageType::Text. Specifically, keep the current
fallback in the parsing code that uses
msg_node.attrs.get("type")...unwrap_or(NewsletterMessageType::Text) but delete
the TODO comment, and add a test for parse_newsletter_messages_response (or a
focused test that calls the message-parsing path) that supplies a node without a
"type" attribute and asserts the resulting enum equals
NewsletterMessageType::Text to prevent regression.

In `@src/handlers/ib.rs`:
- Around line 46-48: The timestamp parsing currently swallows parse errors via
.ok(), so malformed timestamps become None and lead to creating
DirtyBit::new(dirty_type) (changing ack semantics); update the parsing of
attrs.optional_string("timestamp") to explicitly fail on parse errors instead of
using .ok()—parse the string with s.parse::<u64>() and if it Err, return or
propagate an error (reject the notification) rather than falling back to None;
apply the same change to the similar block around lines handling ts/dirty_type
(the other attrs optional_string parsing) so both places treat parse failures as
fatal rather than silent.

In `@wacore/src/iq/prekeys.rs`:
- Around line 272-274: Update the wire-format documentation to match actual
server behavior: change the comment that currently documents "<key>[3-byte BE
prekey ID]</key>" to reflect that the server sends prekey IDs as <id> nodes
(and/or accept both <id> and <key>), so it aligns with the parsing logic which
iterates all children (see mapChildren/the prekey parsing block that parses
children of <list>). Modify the comment at the wire format doc (around the
existing "<key>" note) to state that prekey IDs may appear as <id> nodes (or as
<key>, accept both) to remove the inconsistency.

In `@wacore/src/types/user.rs`:
- Around line 64-71: The BusinessHoursConfig struct in wacore/src/types/user.rs
uses String for open_time/close_time which diverges from
crate::iq::business::BusinessHoursConfig (which uses Option<String>); change the
open_time and close_time fields on the user::BusinessHoursConfig to
Option<String> to unify the shape, then update any
constructors/serialization/usage sites that construct or read
BusinessHoursConfig (search for BusinessHoursConfig, open_time, close_time) to
handle Option<String> (mapping empty-string sentinel logic to None or vice-versa
where interop is necessary) and add/adjust any unit conversions or tests to
ensure compatibility with crate::iq::business::BusinessHoursConfig.

---

Outside diff comments:
In `@wacore/src/iq/dirty.rs`:
- Around line 60-73: Introduce a strict raw parser on DirtyBit by adding
DirtyBit::from_raw(dirty_type: &str, timestamp: Option<&str>) ->
Result<DirtyBit, DirtyBitParseError> that parses the timestamp and returns a
typed error implemented with thiserror (DirtyBitParseError) instead of using
anyhow; move the timestamp parsing logic currently in
CleanDirtyBitsSpec::from_raw into that new DirtyBit::from_raw and have
CleanDirtyBitsSpec::from_raw delegate to DirtyBit::from_raw to construct its
bits, then update Client::clean_dirty_bits() call sites (including
src/handlers/ib.rs) to use DirtyBit::from_raw and propagate/report the typed
error instead of duplicating parsing or silently dropping invalid timestamps via
.ok().
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 352d09ca-0c51-4b5a-94fc-bf9d12a044ff

📥 Commits

Reviewing files that changed from the base of the PR and between 44f6ba5 and 4860842.

📒 Files selected for processing (15)
  • src/features/newsletter.rs
  • src/handlers/ib.rs
  • src/message.rs
  • src/pdo.rs
  • src/receipt.rs
  • src/retry.rs
  • wacore/src/iq/business.rs
  • wacore/src/iq/dirty.rs
  • wacore/src/iq/mediaconn.rs
  • wacore/src/iq/prekeys.rs
  • wacore/src/iq/privacy.rs
  • wacore/src/messages.rs
  • wacore/src/stanza/receipt.rs
  • wacore/src/types/message.rs
  • wacore/src/types/user.rs

Comment thread src/features/newsletter.rs Outdated
Comment thread src/handlers/ib.rs Outdated
Comment thread wacore/src/iq/prekeys.rs Outdated
Comment thread wacore/src/types/user.rs Outdated
Comment on lines 64 to 71
// TODO: consolidate with crate::iq::business::BusinessHoursConfig which uses Option<String>
// for open_time/close_time instead of plain String.
#[derive(Debug, Clone)]
pub struct BusinessHoursConfig {
pub day_of_week: String,
pub mode: String,
pub day_of_week: crate::iq::business::DayOfWeek,
pub mode: crate::iq::business::BusinessHourMode,
pub open_time: String,
pub close_time: String,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Unify BusinessHoursConfig shape to avoid lossy conversions.

This struct still diverges from wacore/src/iq/business.rs:86-94 (String vs Option<String> for time fields), which keeps two incompatible nominal models for the same concept and can leak empty-string sentinel logic into callers.

Proposed alignment
-// TODO: consolidate with crate::iq::business::BusinessHoursConfig which uses Option<String>
-// for open_time/close_time instead of plain String.
 #[derive(Debug, Clone)]
 pub struct BusinessHoursConfig {
     pub day_of_week: crate::iq::business::DayOfWeek,
     pub mode: crate::iq::business::BusinessHourMode,
-    pub open_time: String,
-    pub close_time: String,
+    pub open_time: Option<String>,
+    pub close_time: Option<String>,
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: consolidate with crate::iq::business::BusinessHoursConfig which uses Option<String>
// for open_time/close_time instead of plain String.
#[derive(Debug, Clone)]
pub struct BusinessHoursConfig {
pub day_of_week: String,
pub mode: String,
pub day_of_week: crate::iq::business::DayOfWeek,
pub mode: crate::iq::business::BusinessHourMode,
pub open_time: String,
pub close_time: String,
#[derive(Debug, Clone)]
pub struct BusinessHoursConfig {
pub day_of_week: crate::iq::business::DayOfWeek,
pub mode: crate::iq::business::BusinessHourMode,
pub open_time: Option<String>,
pub close_time: Option<String>,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wacore/src/types/user.rs` around lines 64 - 71, The BusinessHoursConfig
struct in wacore/src/types/user.rs uses String for open_time/close_time which
diverges from crate::iq::business::BusinessHoursConfig (which uses
Option<String>); change the open_time and close_time fields on the
user::BusinessHoursConfig to Option<String> to unify the shape, then update any
constructors/serialization/usage sites that construct or read
BusinessHoursConfig (search for BusinessHoursConfig, open_time, close_time) to
handle Option<String> (mapping empty-string sentinel logic to None or vice-versa
where interop is necessary) and add/adjust any unit conversions or tests to
ensure compatibility with crate::iq::business::BusinessHoursConfig.

…m types/user.rs

These types had zero references across the codebase. The canonical
BusinessHoursConfig (with DayOfWeek/BusinessHourMode enums) lives
in wacore/src/iq/business.rs and is the only one used.
…rekey docs

DirtyBit::from_raw() with typed DirtyBitParseError (thiserror):
- Moved timestamp parsing logic from CleanDirtyBitsSpec into DirtyBit::from_raw()
- CleanDirtyBitsSpec::from_raw() now delegates to DirtyBit::from_raw()
- IB handler now uses DirtyBit::from_raw() and rejects (warns+skips)
  malformed timestamps instead of silently treating them as None

Newsletter:
- Removed TODO comment, added unit tests locking the fallback behavior:
  missing type attribute defaults to NewsletterMessageType::Text

Prekeys:
- Fixed wire-format doc: server sends prekey IDs as <id> nodes, not <key>
Only keep PreKeyFetchReason enum. The digest <key> vs <id> fix
will be done in a separate PR.
@jlucaso1
jlucaso1 merged commit dc645a1 into main Mar 26, 2026
11 of 12 checks passed
@jlucaso1
jlucaso1 deleted the refactor/type-safety-audit branch March 26, 2026 02:18
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