Skip to content

Refactor codebase to reduce duplication#239

Merged
dannym-arx merged 24 commits intomasterfrom
clean-up-code
Apr 2, 2026
Merged

Refactor codebase to reduce duplication#239
dannym-arx merged 24 commits intomasterfrom
clean-up-code

Conversation

@dannym-arx
Copy link
Copy Markdown
Contributor

@dannym-arx dannym-arx commented Mar 27, 2026

marmot

What this does

Large-scale refactor across the MDK workspace to eliminate hand-written boilerplate. Introduces a new mdk-macros crate as the colony's stamp press — generating setters, getters, state enums, pagination types, storage errors, and cross-backend test harnesses so no marmot has to write the same tunnel twice.

Changes

New crate: mdk-macros

  • setters!, mut_setters!, ref_getters! — builder and accessor generation
  • string_enum!, bounded_pagination!, storage_error! — type and error standardization
  • storage_backend_tests! — shared test harness macro; cross-backend test wrappers collapse to a single line

Validation consolidation (mdk-storage-traits)

  • Shared groups::validation and welcomes::validation modules; both memory and SQLite backends call the same validators instead of duplicating the logic

SQLite cleanup (mdk-sqlite-storage)

  • blob_to_* / text_to_* parsing helpers centralized
  • db_error_fn! macro replaces repeated into_*_err boilerplate

Memory storage cleanup (mdk-memory-storage)

  • mls_store_base!, mls_single_key_ops!, validation_limits!, StorageProvider generators remove ~300 lines of MLS in-memory store scaffolding

Credential hardening (mdk-core)

  • pubkey_from_credential centralizes MLS credential public-key extraction; all pubkey lookup paths now go through one non-public helper

Test reorganization

  • Shared test code moved to mdk-core/tests/shared; per-backend wrappers replaced with storage_backend_tests! invocations

What didn't change

  • No cryptographic algorithm changes
  • No MLS protocol or state-machine changes
  • No NIP-level semantic changes to Nostr logic
  • No intentional breaking changes to public method signatures or UniFFI exports

Reviewers: pay attention to

  • pubkey_from_credential — centralized identity binding for MLS credentials; confirm error semantics are preserved
  • Macro-generated pagination and state types — subtle defaulting or validation differences vs. previous hand-written constructors
  • Removed Display implementations for storage errors — verify no sensitive identifiers are now leaked or obscured

This PR is a large-scale refactor that eliminates hand-written boilerplate across the MDK workspace by introducing generator macros, centralizing validation, and consolidating test harnesses to reduce duplicated code and maintenance burden. It moves many repetitive implementations into macro expansions (new crate mdk-macros) and shared helper modules so that memory and SQLite backends reuse the same validation and parsing logic while preserving protocol and cryptographic behavior.

What changed:

  • Introduced a new crate mdk-macros providing macros for setters/mut setters/ref-getters and reduced boilerplate across the workspace.
  • Added workspace dependency pastey and a new internal crate mdk-macros (crates/mdk-macros).
  • Centralized group/welcome validation into mdk-storage-traits::groups::validation and ::welcomes::validation and migrated backends to call those helpers.
  • Replaced manually written string/enumeration, pagination, and error boilerplate with macros in mdk-storage-traits (string_enum!, bounded_pagination!, storage_error!).
  • Centralized SQLite row parsing and fixed-size blob newtypes in mdk-sqlite-storage (sqlite_blob_newtype!, blob/text helpers, db_error_fn!).
  • Replaced repetitive MLS in-memory store code with macros in mdk-memory-storage (mls_store_base!, mls_single_key_ops!, validation_limits!, StorageProvider macro usage).
  • Centralized MLS credential public-key extraction in mdk-core via pubkey_from_credential and replaced many hand-written setters/getters with macro-generated accessors.
  • Reorganized tests: moved shared test helpers into mdk-core/tests/shared and replaced per-backend test wiring with storage_backend_tests! invocations.

Security impact:

  • No changes to cryptographic algorithms, key derivation, nonces, HKDF contexts, SQLCipher configuration, or file permissions.
  • Key handling/credential extraction was centralized (pubkey_from_credential) which reduces duplication and risk of inconsistent parsing/validation semantics. Reviewers should confirm the helper’s error semantics match prior behavior.
  • Storage error Display/Display-like implementations were replaced by macro/thiserror-generated errors (many explicit Display impls were removed); reviewers should confirm no sensitive identifiers are now exposed or obscured differently by the new error formatting.

Protocol changes:

  • No changes to MLS protocol state-machine or cryptographic protocol behavior (no RFC 9420 changes).
  • No changes to Nostr/NIP semantics or MIP implementations.

API surface:

  • No intentional breaking changes to production public method signatures or UniFFI exports were introduced; macro-generated implementations preserve existing public APIs in the diffs shown.
  • Pagination types and validation semantics were moved to bounded_pagination! in mdk-storage-traits which changes underlying representation/validation behavior (callers relying on previous defaulting semantics should be reviewed).
  • Several test helper functions were removed from their prior locations and testing imports were reorganized (this affects test code and consumers of those helpers).
  • New public macros exported from mdk-macros and mdk-storage-traits (e.g., setters!, mut_setters!, ref_getters!, storage_backend_tests!, impl_exporter_secret_methods!) expand the public API surface for macro usage.

Testing:

  • Tests were reorganized: many cross-backend test helpers (~700 lines) moved and per-backend test registrations replaced with storage_backend_tests! to generate backend tests.
  • string_enum! injects autogenerated unit tests (via pastey) for enum roundtrips; many prior manual enum/display tests were removed.
  • No tests indicating changed cryptographic or protocol behavior were added or removed; reviewers should run the test suite to validate macro-generated behavior matches previous semantics.

@dannym-arx dannym-arx marked this pull request as draft March 27, 2026 15:00
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 27, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds a new mdk-macros crate and workspace pastey dependency; replaces many handwritten enums, errors, serde, pagination, and setter/getter implementations with macro-generated versions; centralizes storage validation and row-parsing helpers; consolidates MLS store code, test wiring, and UniFFI helper logic.

Changes

Cohort / File(s) Summary
Workspace deps
Cargo.toml, crates/mdk-core/Cargo.toml, crates/mdk-macros/Cargo.toml, crates/mdk-memory-storage/Cargo.toml, crates/mdk-storage-traits/Cargo.toml
Added new workspace crate mdk-macros and introduced pastey as a workspace dependency.
Macro crate
crates/mdk-macros/src/lib.rs, crates/mdk-macros/Cargo.toml
New crate exposing setters!, mut_setters!, ref_getters! and #![forbid(unsafe_code)].
Core: builders, groups, errors, key-packages
crates/mdk-core/src/lib.rs, crates/mdk-core/src/groups.rs, crates/mdk-core/src/error.rs, crates/mdk-core/src/key_packages.rs, crates/mdk-core/src/extension/types.rs, crates/mdk-core/Cargo.toml
Replaced manual builder/getter/setter and many From<...> for Error impls with macro-generated code; added internal helper pubkey_from_credential; simplified key-package tag parsing; added mdk-macros workspace dep.
Core tests
crates/mdk-core/tests/shared/mod.rs, crates/mdk-core/tests/storage_traits_memory.rs, crates/mdk-core/tests/storage_traits_sqlite.rs, crates/mdk-core/tests/shared/*
Removed concrete test-data constructors and per-file test wiring; added/exported storage_backend_tests! macro and replaced local test macros with single macro invocations; adjusted test imports to use shared helpers.
Memory storage
crates/mdk-memory-storage/src/lib.rs, crates/mdk-memory-storage/src/groups.rs, crates/mdk-memory-storage/src/welcomes.rs, crates/mdk-memory-storage/src/mls_storage/mod.rs, crates/mdk-memory-storage/Cargo.toml
Introduced macros to generate MLS store types/ops and StorageProvider read/write/delete methods; centralized validation and exporter-secret methods; added sorting/pagination helpers; added pastey.workspace = true dep.
SQLite storage
crates/mdk-sqlite-storage/src/db.rs, crates/mdk-sqlite-storage/src/groups.rs, crates/mdk-sqlite-storage/src/lib.rs, crates/mdk-sqlite-storage/src/messages.rs, crates/mdk-sqlite-storage/src/welcomes.rs, crates/mdk-sqlite-storage/src/mls_storage/mod.rs
Added sqlite_blob_newtype!, extracted blob_to_*/text_to_* row-parsing helpers, added db_error_fn!, macroized MLS table ops, centralized group-existence checks and validation, and consolidated snapshot helpers.
Storage-traits: enums, pagination, validation
crates/mdk-storage-traits/src/lib.rs, crates/mdk-storage-traits/src/groups/*, crates/mdk-storage-traits/src/messages/*, crates/mdk-storage-traits/src/welcomes/*, crates/mdk-storage-traits/src/test_utils.rs, crates/mdk-storage-traits/Cargo.toml
Added string_enum!, bounded_pagination!, storage_error!; replaced manual enum/Display/FromStr/serde/Pagination/Error implementations with macro-generated equivalents; added groups::validation and welcomes::validation; removed large cross-storage test helpers; added pastey.workspace = true.
UniFFI glue
crates/mdk-uniffi/src/lib.rs, crates/mdk-uniffi/CHANGELOG.md
Factored update_group_result_to_uniffi and mdk_from_storage helpers; added parsing helper macro and simplified iterator collection patterns; consolidated UniFFI constructors.
Changelogs & misc
crates/*/CHANGELOG.md
Updated multiple changelogs to document macro-driven refactors, validation extraction, and consolidation of test wiring/helpers.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

storage, breaking-change

Suggested reviewers

  • jgmontoya
  • erskingardner
  • mubarakcoded
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 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: a large-scale refactor introducing macros and consolidation to eliminate boilerplate and reduce code duplication across the MDK workspace.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
No Sensitive Identifier Leakage ✅ Passed Investigation confirmed no sensitive identifiers leaked through logging, error messages, panic messages, or Debug implementations. Redaction explicitly implemented and validation errors remain generic.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch clean-up-code

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 27, 2026

UniFFI Binding Coverage

10 public method(s) not bound in UniFFI

Coverage: 31/41 bindable methods (75.6%)

⚠️ Not Bound

Method Source
delete_key_package_from_storage crates/mdk-core/src/key_packages.rs:598
delete_key_package_from_storage_by_hash_ref crates/mdk-core/src/key_packages.rs:613
get_ratchet_tree_info crates/mdk-core/src/groups.rs:559
group_leaf_map crates/mdk-core/src/groups.rs:525
own_leaf_index crates/mdk-core/src/groups.rs:516
pending_added_members_pubkeys crates/mdk-core/src/groups.rs:615
pending_member_changes crates/mdk-core/src/groups.rs:683
pending_removed_members_pubkeys crates/mdk-core/src/groups.rs:648
prepare_group_image_for_upload_with_options crates/mdk-core/src/extension/group_image.rs:459
process_message_with_context crates/mdk-core/src/messages/process.rs:327
✅ Bound methods (31)
Method Source
accept_welcome crates/mdk-core/src/welcomes.rs:315
add_members crates/mdk-core/src/groups.rs:729
clear_pending_commit crates/mdk-core/src/groups.rs:1613
create_group crates/mdk-core/src/groups.rs:1167
create_key_package_for_event crates/mdk-core/src/key_packages.rs:54
create_key_package_for_event_with_options crates/mdk-core/src/key_packages.rs:92
create_message crates/mdk-core/src/messages/create.rs:79
decline_welcome crates/mdk-core/src/welcomes.rs:350
decrypt_group_image crates/mdk-core/src/extension/group_image.rs:227
derive_upload_keypair crates/mdk-core/src/extension/group_image.rs:332
get_group crates/mdk-core/src/groups.rs:455
get_groups crates/mdk-core/src/groups.rs:467
get_last_message crates/mdk-core/src/messages/mod.rs:278
get_members crates/mdk-core/src/groups.rs:501
get_message crates/mdk-core/src/messages/mod.rs:211
get_messages crates/mdk-core/src/messages/mod.rs:250
get_pending_welcomes crates/mdk-core/src/welcomes.rs:72
get_relays crates/mdk-core/src/groups.rs:1113
get_welcome crates/mdk-core/src/welcomes.rs:40
groups_needing_self_update crates/mdk-core/src/groups.rs:485
leave_group crates/mdk-core/src/groups.rs:1560
merge_pending_commit crates/mdk-core/src/groups.rs:1664
parse_key_package crates/mdk-core/src/key_packages.rs:268
prepare_group_image_for_upload crates/mdk-core/src/extension/group_image.rs:413
process_message crates/mdk-core/src/messages/process.rs:320
process_welcome crates/mdk-core/src/welcomes.rs:178
remove_members crates/mdk-core/src/groups.rs:827
self_demote crates/mdk-core/src/groups.rs:1506
self_update crates/mdk-core/src/groups.rs:1339
sync_group_metadata_from_mls crates/mdk-core/src/groups.rs:1735
update_group_data crates/mdk-core/src/groups.rs:1047
➖ Intentionally excluded (5)
Method Reason
builder Wrapped by new_mdk(), new_mdk_with_key(), new_mdk_unencrypted() free functions
load_mls_group Only available behind debug-examples feature flag, not for production use
media_manager Returns borrowed reference with lifetime — cannot cross FFI boundary
migrate_group_image_v1_to_v2 Migration utility for internal use only, not needed in client bindings
new Wrapped by new_mdk(), new_mdk_with_key(), new_mdk_unencrypted() free functions

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 91.88% (+0.37%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.19% (+1.68%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.28% (+1.77%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.28% (+1.77%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.29% (+1.78%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.28% (+1.77%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.26% (+1.75%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.32% (+1.81%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.29% (+1.78%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.33% (+1.82%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.32% (+1.81%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.33% (+1.82%)

@dannym-arx dannym-arx marked this pull request as ready for review March 27, 2026 18:19
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (2)
crates/mdk-sqlite-storage/src/welcomes.rs (1)

20-56: ⚠️ Potential issue | 🟠 Major

Call validate_welcome_fields from the SQLite backend too.

Line 20 still only validates name/description lengths and serialized JSON sizes. Since the relay/admin/url rules now live in crates/mdk-storage-traits/src/welcomes/validation.rs, SQLite can accept welcomes that the memory backend rejects whenever those inputs stay under the JSON byte caps. Please run the shared helper here before serialization and add a SQLite regression test for relay count, relay URL length, and admin count.

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

In `@crates/mdk-sqlite-storage/src/welcomes.rs` around lines 20 - 56, Call the
shared validation helper validate_welcome_fields from crates/mdk-storage-traits
(the function named validate_welcome_fields) at the start of the SQLite save
flow in welcomes.rs before serializing admin pubkeys and relays (i.e. before
serde_json::to_string calls that create group_admin_pubkeys_json and
group_relays_json), propagating its error into WelcomeError::InvalidParameters
as done elsewhere; then add SQLite regression tests that exercise relay count,
relay URL length, and admin count limits to ensure the SQLite backend rejects
inputs the memory backend already does.
crates/mdk-core/src/key_packages.rs (1)

408-437: ⚠️ Potential issue | 🟠 Major

Reject extra values on single-valued key-package tags.

tag_first_value() makes mls_protocol_version and mls_ciphersuite accept tags like ["mls_protocol_version", "1.0", "junk"] and ["mls_ciphersuite", "0x0001", "junk"]. That weakens the MIP-00 validation here and can leave malformed events valid locally while other implementations reject them.

🔐 Proposed fix
+    fn tag_single_value<'a>(
+        tag: &'a Tag,
+        missing_msg: &str,
+        multi_value_msg: &str,
+    ) -> Result<&'a str, Error> {
+        match tag.as_slice() {
+            [_, value] => Ok(value.as_str()),
+            [_] => Err(Error::KeyPackage(missing_msg.to_string())),
+            _ => Err(Error::KeyPackage(multi_value_msg.to_string())),
+        }
+    }
+
     /// Validates protocol version tag format and value.
     ///
     /// **SPEC-COMPLIANT**: Per MIP-00, only "1.0" is currently supported.
     fn validate_protocol_version_tag(&self, tag: &Tag) -> Result<(), Error> {
-        let version_value = Self::tag_first_value(tag, "Protocol version tag must have a value")?;
+        let version_value = Self::tag_single_value(
+            tag,
+            "Protocol version tag must have a value",
+            "Protocol version tag must contain exactly one value",
+        )?;
@@
     /// Validates ciphersuite tag format and value per MIP-00.
     ///
     /// Currently only accepts: "0x0001" (MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519)
     fn validate_ciphersuite_tag(&self, tag: &Tag) -> Result<(), Error> {
-        let ciphersuite_value = Self::tag_first_value(tag, "Ciphersuite tag must have a value")?;
+        let ciphersuite_value = Self::tag_single_value(
+            tag,
+            "Ciphersuite tag must have a value",
+            "Ciphersuite tag must contain exactly one value",
+        )?;

Based on learnings, "Ensure all code follows the Marmot Protocol specification exactly. Ask clarifying questions if any part of the spec is unclear".

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

In `@crates/mdk-core/src/key_packages.rs` around lines 408 - 437, tag_first_value
currently returns the first value but allows extra entries (e.g.
["mls_protocol_version","1.0","junk"]); change tag_first_value to verify the tag
has exactly two elements (name and single value) and return
Err(Error::KeyPackage(...)) when len != 2 so single-valued tags are rejected;
ensure validate_protocol_version_tag and validate_ciphersuite_tag continue to
call tag_first_value and rely on its stricter check, and update the error
message produced by tag_first_value to indicate unexpected extra values for
clarity.
🧹 Nitpick comments (8)
crates/mdk-memory-storage/src/lib.rs (1)

165-176: Carry the field docs onto the generated with_* setters.

These public builders currently only expose a generic # Panics section, so rustdoc doesn't say what each setter actually configures. Reusing the field docs here keeps the public API readable after the macro refactor.

📝 Suggested rustdoc fix
             $(
                 pastey::paste! {
+                    $(#[doc = $doc])*
+                    ///
                     /// # Panics
                     ///
                     /// Panics if `limit` is 0.
                     pub fn [<with_ $field>](mut self, limit: usize) -> Self {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-memory-storage/src/lib.rs` around lines 165 - 176, The generated
builder setters [<with_ $field>] lack the original field documentation; update
the macro that emits the with_* functions so it reuses the field's doc
attributes when generating each setter (i.e. carry the field doc attrs into the
function), by adding the existing field doc meta (e.g. the #[doc = ...] /
#[$field_doc] entries) to the generated pub fn [<with_ $field>](...) so rustdoc
shows what the setter configures while keeping the existing panic note.
crates/mdk-sqlite-storage/src/groups.rs (2)

24-34: Use a lightweight existence probe here instead of deserializing the full group.

This helper currently does SELECT * and full row decoding just to answer “does the group exist?”. That adds avoidable work to hot paths like messages() / last_message() and also makes unrelated metadata corruption block relay/message lookups.

♻️ Proposed refactor
 #[inline]
 fn ensure_group_exists(
     storage: &MdkSqliteStorage,
     mls_group_id: &GroupId,
 ) -> Result<(), GroupError> {
-    if storage.find_group_by_mls_group_id(mls_group_id)?.is_some() {
-        Ok(())
-    } else {
-        Err(group_not_found())
-    }
+    let exists = storage.with_connection(|conn| {
+        conn.query_row(
+            "SELECT 1 FROM groups WHERE mls_group_id = ? LIMIT 1",
+            params![mls_group_id.as_slice()],
+            |_| Ok(()),
+        )
+        .optional()
+        .map_err(into_group_err)
+    })?;
+
+    exists.ok_or_else(group_not_found)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-sqlite-storage/src/groups.rs` around lines 24 - 34, The helper
ensure_group_exists should avoid deserializing the full group row; replace the
call to storage.find_group_by_mls_group_id(...) with a lightweight existence
probe (e.g., add and call a new storage method like has_group_with_mls_group_id
or exists_group_by_mls_group_id that performs SELECT 1 / EXISTS ... LIMIT 1) and
return Ok(()) when that probe is true, otherwise Err(group_not_found()).
Implement the new storage method to run a minimal SQL existence check (no full
row decode) and update ensure_group_exists to use that method (retain GroupId
and GroupError symbols).

291-333: Run the shared relay validation before mutating the table.

validate_relay_set() was added to centralize relay-count and URL-length rules, but this path still deletes and reinserts without calling it. That leaves SQLite with different acceptance rules from the shared validation module and pushes oversized relay sets to fail later at the DB layer instead of as InvalidParameters.

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

In `@crates/mdk-sqlite-storage/src/groups.rs` around lines 291 - 333, The
replace_group_relays function mutates the DB without first running the shared
validation, so oversized or invalid relay sets slip past and later fail at the
SQLite layer; before deleting/inserting rows in replace_group_relays call
validate_relay_set(&relays) (and propagate its InvalidParameters error into the
GroupError path) so validation runs early (i.e., perform the validate_relay_set
check immediately after ensure_group_exists and before any DB
execute_batch/delete/insert work).
crates/mdk-sqlite-storage/src/db.rs (1)

160-164: Preserve the enum-specific parse error here.

This helper now collapses every bad state in every table into the same "Invalid state" string, which makes corrupted rows much harder to diagnose and throws away the string_enum! messages you just centralized. Forward T::Err through map_to_text_boxed_error instead.

♻️ Proposed refactor
-fn text_to_state<T: FromStr>(row: &Row, col: &str) -> SqliteResult<T> {
+fn text_to_state<T>(row: &Row, col: &str) -> SqliteResult<T>
+where
+    T: FromStr,
+    T::Err: std::error::Error + Send + Sync + 'static,
+{
     let s = row.get_ref(col)?.as_str()?;
-    T::from_str(s).map_err(|_| map_invalid_text_data("Invalid state"))
+    T::from_str(s).map_err(map_to_text_boxed_error)
 }

As per coding guidelines, "All trait bounds in where clauses, not inline."

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

In `@crates/mdk-sqlite-storage/src/db.rs` around lines 160 - 164, The helper
text_to_state currently maps all parse errors to the generic "Invalid state"
string; change it to preserve and forward the enum-specific parse error by
returning T::Err converted through map_to_text_boxed_error instead of the fixed
message. Update the function signature to put the FromStr bound in a where
clause (e.g., fn text_to_state<T>(row: &Row, col: &str) -> SqliteResult<T> where
T: FromStr, and then use T::from_str(s).map_err(map_to_text_boxed_error) so the
original T::Err is preserved and boxed).
crates/mdk-core/src/error.rs (1)

251-280: Keep the generic bounds in where clauses in the generated impls.

The new macroized conversions reintroduce inline bounds (impl<T: fmt::Display> ...) in both the macro and the explicit CommitToPendingProposalsError impl. Please move those to where clauses so the generated code matches the repo’s Rust style.

♻️ Proposed refactor
 macro_rules! impl_from_generic_display_error {
     ($($source:ident => $variant:ident),+ $(,)?) => {
         $(
-            impl<T: fmt::Display> From<$source<T>> for Error {
+            impl<T> From<$source<T>> for Error
+            where
+                T: fmt::Display,
+            {
                 fn from(e: $source<T>) -> Self {
                     Self::$variant(e.to_string())
                 }
             }
         )+
@@
-impl<T: fmt::Display> From<CommitToPendingProposalsError<T>> for Error {
+impl<T> From<CommitToPendingProposalsError<T>> for Error
+where
+    T: fmt::Display,
+{
     fn from(_e: CommitToPendingProposalsError<T>) -> Self {
         Self::CommitToPendingProposalsError
     }
 }

As per coding guidelines, "All trait bounds in where clauses, not inline."

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

In `@crates/mdk-core/src/error.rs` around lines 251 - 280, The generated impls
currently use inline bounds like `impl<T: fmt::Display> From<...>`; change them
to use `where` clauses instead—update the macro
`impl_from_generic_display_error!` (and the `impl_from_display_error!` usage if
needed) so it emits `impl<T> From<$source<T>> for Error where T: fmt::Display {
... }`, and modify the explicit impl for `From<CommitToPendingProposalsError<T>>
for Error` to `impl<T> From<CommitToPendingProposalsError<T>> for Error where T:
fmt::Display { ... }`; ensure all trait bounds are moved from inline `impl<...>`
headers into `where` clauses to match the repo style.
crates/mdk-storage-traits/src/groups/validation.rs (1)

13-76: Add direct tests around these shared boundary checks.

This module is now the validation source of truth for multiple storage backends, but the new file has no local tests for exact-limit / over-limit cases on name, description, admin count, relay count, and relay URL length. A small table-driven suite here would catch drift before it hits both backends.

As per coding guidelines, "ensure CI test coverage does not decrease: run the appropriate just test* targets and add/adjust tests if any new paths were introduced or existing paths lost coverage due to macro consolidation."

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

In `@crates/mdk-storage-traits/src/groups/validation.rs` around lines 13 - 76, Add
unit tests for validate_group_fields and validate_relay_set that exercise
exact-limit and over-limit cases for name length, description length,
admin_pubkeys count, relay count, and individual relay URL length; create a
table-driven test table that constructs Group instances (testing
validate_group_fields) and BTreeSet<RelayUrl> instances (testing
validate_relay_set), asserting Ok for values equal to the limits and
Err(GroupError::InvalidParameters) for values exceeding limits; use the existing
types Group, RelayUrl, GroupError, and call validate_group_fields and
validate_relay_set directly in the tests to prevent regressions when storage
backends rely on these shared validators.
crates/mdk-core/src/groups.rs (1)

168-177: Use .to_owned() in generated string setters to match repository style rules.

The into_string setter currently uses .into() for String conversion; this conflicts with the project rule for string conversions.

♻️ Suggested adjustment
-    (into_string: $name:ident, $doc:expr) => {
+    (into_string: $name:ident, $doc:expr) => {
         #[doc = $doc]
         pub fn $name<T>(mut self, $name: T) -> Self
         where
-            T: Into<String>,
+            T: AsRef<str>,
         {
-            self.$name = Some($name.into());
+            self.$name = Some($name.as_ref().to_owned());
             self
         }
     };

As per coding guidelines: use .to_string() or .to_owned(), not .into() or String::from for string conversions.

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

In `@crates/mdk-core/src/groups.rs` around lines 168 - 177, The generated setter
in the update_setter! macro (the into_string: $name variant) uses $name.into()
to convert to String; change that conversion to $name.to_owned() (or
to_string()) so the setter assigns self.$name = Some($name.to_owned()) to
conform to the repository string-conversion style and keep the macro-generated
setters consistent.
crates/mdk-uniffi/src/lib.rs (1)

259-289: Add one happy-path test for this shared UpdateGroupResult mapper.

This helper now defines the public output shape for add_members, remove_members, self_update, leave_group, update_group_data, and the proposal branch of process_message(), but nothing in this file asserts the serialized evolution_event_json / welcome_rumors_json / mls_group_id contract. One focused test here would cover all of those paths at once.

As per coding guidelines, ensure CI test coverage does not decrease and add or adjust tests when new paths are introduced.

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

In `@crates/mdk-uniffi/src/lib.rs` around lines 259 - 289, Add a focused
happy-path unit test that constructs an mdk_core::groups::UpdateGroupResult with
a concrete evolution_event, an Option<Vec<welcome_rumor>> containing at least
one rumor, and a known mls_group_id byte array, then call
update_group_result_to_uniffi and assert the returned UpdateGroupResult has
evolution_event_json that parses back to the original evolution_event (or
contains expected fields), welcome_rumors_json is Some with each entry parsing
back to the original rumors, and mls_group_id equals hex::encode of the original
bytes; reference the function update_group_result_to_uniffi and the exported
UpdateGroupResult shape when adding the test to ensure the serialized JSON/hex
contract is validated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/mdk-core/CHANGELOG.md`:
- Around line 31-32: Update the two changelog bullets shown (the one about
replacing ~15 identical `From<...> for Error` impls with
`impl_from_display_error!` and `impl_from_generic_display_error!` macros, and
the one about consolidating duplicate test wiring into `storage_backend_tests!`
macro) to append the PR reference suffix in the required format, e.g.
`([`#239`](https://github.com/marmot-protocol/mdk/pull/239))`; ensure each bullet
ends with its respective PR link and use the same bracket/parentheses syntax for
both entries.

In `@crates/mdk-memory-storage/CHANGELOG.md`:
- Around line 32-33: Update the two changelog bullets in CHANGELOG.md that start
with "Replaced 6 identical MLS storage..." and "Migrated group and welcome
storage..." to append the PR reference in the required format; add "
([`#239`](https://github.com/marmot-protocol/mdk/pull/239))" at the end of each
bullet so both entries end with the trailing PR link as per the guidelines.

In `@crates/mdk-sqlite-storage/CHANGELOG.md`:
- Around line 32-33: Update the two new CHANGELOG.md bullets to include the
required PR reference suffix
"([`#239`](https://github.com/marmot-protocol/mdk/pull/239))": append this exact
suffix to the end of the "Extracted row-parsing helpers..." bullet (which
mentions blob_to_group_id, blob_to_event_id, blob_to_pubkey, text_to_state,
text_to_json, text_to_parsed and row_to_* functions) and to the "Migrated group
and welcome storage..." bullet (which references mdk-storage-traits), ensuring
both entries follow the project's changelog format.

In `@crates/mdk-storage-traits/CHANGELOG.md`:
- Around line 34-38: Update each Unreleased changelog bullet to append the PR
reference suffix in the required format; for the bullets mentioning
GroupState/MessageState/WelcomeState/ProcessedMessageState/ProcessedWelcomeState
(string_enum!), Pagination/bounded_pagination!,
GroupError/MessageError/WelcomeError (storage_error!), groups::validation and
welcomes::validation extraction, and the removed test_utils::cross_storage
lines, add " ([`#239`](https://github.com/marmot-protocol/mdk/pull/239))" to the
end of each entry so they match the repository changelog format.

In `@crates/mdk-uniffi/CHANGELOG.md`:
- Around line 32-33: Append the PR citation suffix
"([`#239`](https://github.com/marmot-protocol/mdk/pull/239))" to the end of both
changelog bullets currently reading "Extracted `update_group_result_to_uniffi()`
and `mdk_from_storage()` helpers to eliminate duplicated serialization and
constructor logic across multiple exported functions." and "Simplified iterator
collection patterns to use `.collect::<Result<_, _>>()` instead of two-step
collect-then-unwrap.", ensuring each bullet ends with the exact PR reference per
the changelog guideline.

---

Outside diff comments:
In `@crates/mdk-core/src/key_packages.rs`:
- Around line 408-437: tag_first_value currently returns the first value but
allows extra entries (e.g. ["mls_protocol_version","1.0","junk"]); change
tag_first_value to verify the tag has exactly two elements (name and single
value) and return Err(Error::KeyPackage(...)) when len != 2 so single-valued
tags are rejected; ensure validate_protocol_version_tag and
validate_ciphersuite_tag continue to call tag_first_value and rely on its
stricter check, and update the error message produced by tag_first_value to
indicate unexpected extra values for clarity.

In `@crates/mdk-sqlite-storage/src/welcomes.rs`:
- Around line 20-56: Call the shared validation helper validate_welcome_fields
from crates/mdk-storage-traits (the function named validate_welcome_fields) at
the start of the SQLite save flow in welcomes.rs before serializing admin
pubkeys and relays (i.e. before serde_json::to_string calls that create
group_admin_pubkeys_json and group_relays_json), propagating its error into
WelcomeError::InvalidParameters as done elsewhere; then add SQLite regression
tests that exercise relay count, relay URL length, and admin count limits to
ensure the SQLite backend rejects inputs the memory backend already does.

---

Nitpick comments:
In `@crates/mdk-core/src/error.rs`:
- Around line 251-280: The generated impls currently use inline bounds like
`impl<T: fmt::Display> From<...>`; change them to use `where` clauses
instead—update the macro `impl_from_generic_display_error!` (and the
`impl_from_display_error!` usage if needed) so it emits `impl<T>
From<$source<T>> for Error where T: fmt::Display { ... }`, and modify the
explicit impl for `From<CommitToPendingProposalsError<T>> for Error` to `impl<T>
From<CommitToPendingProposalsError<T>> for Error where T: fmt::Display { ... }`;
ensure all trait bounds are moved from inline `impl<...>` headers into `where`
clauses to match the repo style.

In `@crates/mdk-core/src/groups.rs`:
- Around line 168-177: The generated setter in the update_setter! macro (the
into_string: $name variant) uses $name.into() to convert to String; change that
conversion to $name.to_owned() (or to_string()) so the setter assigns self.$name
= Some($name.to_owned()) to conform to the repository string-conversion style
and keep the macro-generated setters consistent.

In `@crates/mdk-memory-storage/src/lib.rs`:
- Around line 165-176: The generated builder setters [<with_ $field>] lack the
original field documentation; update the macro that emits the with_* functions
so it reuses the field's doc attributes when generating each setter (i.e. carry
the field doc attrs into the function), by adding the existing field doc meta
(e.g. the #[doc = ...] / #[$field_doc] entries) to the generated pub fn [<with_
$field>](...) so rustdoc shows what the setter configures while keeping the
existing panic note.

In `@crates/mdk-sqlite-storage/src/db.rs`:
- Around line 160-164: The helper text_to_state currently maps all parse errors
to the generic "Invalid state" string; change it to preserve and forward the
enum-specific parse error by returning T::Err converted through
map_to_text_boxed_error instead of the fixed message. Update the function
signature to put the FromStr bound in a where clause (e.g., fn
text_to_state<T>(row: &Row, col: &str) -> SqliteResult<T> where T: FromStr, and
then use T::from_str(s).map_err(map_to_text_boxed_error) so the original T::Err
is preserved and boxed).

In `@crates/mdk-sqlite-storage/src/groups.rs`:
- Around line 24-34: The helper ensure_group_exists should avoid deserializing
the full group row; replace the call to storage.find_group_by_mls_group_id(...)
with a lightweight existence probe (e.g., add and call a new storage method like
has_group_with_mls_group_id or exists_group_by_mls_group_id that performs SELECT
1 / EXISTS ... LIMIT 1) and return Ok(()) when that probe is true, otherwise
Err(group_not_found()). Implement the new storage method to run a minimal SQL
existence check (no full row decode) and update ensure_group_exists to use that
method (retain GroupId and GroupError symbols).
- Around line 291-333: The replace_group_relays function mutates the DB without
first running the shared validation, so oversized or invalid relay sets slip
past and later fail at the SQLite layer; before deleting/inserting rows in
replace_group_relays call validate_relay_set(&relays) (and propagate its
InvalidParameters error into the GroupError path) so validation runs early
(i.e., perform the validate_relay_set check immediately after
ensure_group_exists and before any DB execute_batch/delete/insert work).

In `@crates/mdk-storage-traits/src/groups/validation.rs`:
- Around line 13-76: Add unit tests for validate_group_fields and
validate_relay_set that exercise exact-limit and over-limit cases for name
length, description length, admin_pubkeys count, relay count, and individual
relay URL length; create a table-driven test table that constructs Group
instances (testing validate_group_fields) and BTreeSet<RelayUrl> instances
(testing validate_relay_set), asserting Ok for values equal to the limits and
Err(GroupError::InvalidParameters) for values exceeding limits; use the existing
types Group, RelayUrl, GroupError, and call validate_group_fields and
validate_relay_set directly in the tests to prevent regressions when storage
backends rely on these shared validators.

In `@crates/mdk-uniffi/src/lib.rs`:
- Around line 259-289: Add a focused happy-path unit test that constructs an
mdk_core::groups::UpdateGroupResult with a concrete evolution_event, an
Option<Vec<welcome_rumor>> containing at least one rumor, and a known
mls_group_id byte array, then call update_group_result_to_uniffi and assert the
returned UpdateGroupResult has evolution_event_json that parses back to the
original evolution_event (or contains expected fields), welcome_rumors_json is
Some with each entry parsing back to the original rumors, and mls_group_id
equals hex::encode of the original bytes; reference the function
update_group_result_to_uniffi and the exported UpdateGroupResult shape when
adding the test to ensure the serialized JSON/hex contract is validated.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f312f7a2-63f0-4ac4-bbf9-7222d42d06db

📥 Commits

Reviewing files that changed from the base of the PR and between a6ac020 and 70d9bf0.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • Cargo.toml
  • crates/mdk-core/CHANGELOG.md
  • crates/mdk-core/src/error.rs
  • crates/mdk-core/src/groups.rs
  • crates/mdk-core/src/key_packages.rs
  • crates/mdk-core/tests/shared/mod.rs
  • crates/mdk-core/tests/storage_traits_memory.rs
  • crates/mdk-core/tests/storage_traits_sqlite.rs
  • crates/mdk-memory-storage/CHANGELOG.md
  • crates/mdk-memory-storage/Cargo.toml
  • crates/mdk-memory-storage/src/groups.rs
  • crates/mdk-memory-storage/src/lib.rs
  • crates/mdk-memory-storage/src/mls_storage/mod.rs
  • crates/mdk-memory-storage/src/welcomes.rs
  • crates/mdk-sqlite-storage/CHANGELOG.md
  • crates/mdk-sqlite-storage/src/db.rs
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-sqlite-storage/src/lib.rs
  • crates/mdk-sqlite-storage/src/messages.rs
  • crates/mdk-sqlite-storage/src/welcomes.rs
  • crates/mdk-storage-traits/CHANGELOG.md
  • crates/mdk-storage-traits/src/groups/error.rs
  • crates/mdk-storage-traits/src/groups/mod.rs
  • crates/mdk-storage-traits/src/groups/types.rs
  • crates/mdk-storage-traits/src/groups/validation.rs
  • crates/mdk-storage-traits/src/lib.rs
  • crates/mdk-storage-traits/src/messages/error.rs
  • crates/mdk-storage-traits/src/messages/types.rs
  • crates/mdk-storage-traits/src/test_utils.rs
  • crates/mdk-storage-traits/src/welcomes/error.rs
  • crates/mdk-storage-traits/src/welcomes/mod.rs
  • crates/mdk-storage-traits/src/welcomes/types.rs
  • crates/mdk-storage-traits/src/welcomes/validation.rs
  • crates/mdk-uniffi/CHANGELOG.md
  • crates/mdk-uniffi/src/lib.rs

Comment thread crates/mdk-core/CHANGELOG.md Outdated
Comment thread crates/mdk-memory-storage/CHANGELOG.md Outdated
Comment thread crates/mdk-sqlite-storage/CHANGELOG.md Outdated
Comment thread crates/mdk-storage-traits/CHANGELOG.md Outdated
Comment thread crates/mdk-uniffi/CHANGELOG.md Outdated
@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.64% (+2.13%)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/mdk-core/src/groups.rs`:
- Around line 174-178: The setters macro invocation mdk_macros::setters!
currently uses "name: impl Into<String>;" and "description: impl Into<String>;"
which expands to .into() calls; change both parameter types to accept String
directly (e.g., "name: String;" and "description: String;") so the generated
setters use owned Strings and avoid .into(); if &str call sites are common,
consider extending mdk_macros::setters! with a dedicated string arm that emits
.to_owned() instead of .into() to preserve ergonomics.

In `@crates/mdk-macros/src/lib.rs`:
- Around line 1-20: The crate root (crates/mdk-macros/src/lib.rs) is missing the
workspace unsafe gate; add a crate-level directive at the top of lib.rs such as
#![forbid(unsafe_code)] (or #![deny(unsafe_code)] if preferred) so the macros
crate inherits the repo-wide prohibition of unsafe code; place it before any
item (e.g., before the doc comment and before #[macro_export]) to ensure it
applies to the whole crate.
- Around line 24-90: The macro arms that generate impl Into setters use the
generic parameter name T which can shadow a surrounding generic and trigger
E0403; update all four "impl Into" arms in both the setters! and mut_setters!
macros to replace the generic parameter T with a private, unlikely-to-conflict
identifier (for example __MdkIntoValue) and update the where clause and
parameter usage accordingly (e.g., pub fn $method<__MdkIntoValue>(mut self,
$field: __MdkIntoValue) -> Self where __MdkIntoValue: Into<$inner> { ... } ),
making the same replacement for $name/$method variants and for the
direct/renamed forms so no generic name collision can occur.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a639f9de-aa89-4034-b733-c674dbf841f0

📥 Commits

Reviewing files that changed from the base of the PR and between 33c82fa and 1f4651e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • Cargo.toml
  • crates/mdk-core/Cargo.toml
  • crates/mdk-core/src/extension/types.rs
  • crates/mdk-core/src/groups.rs
  • crates/mdk-core/src/lib.rs
  • crates/mdk-macros/Cargo.toml
  • crates/mdk-macros/src/lib.rs
  • crates/mdk-uniffi/src/lib.rs
✅ Files skipped from review due to trivial changes (2)
  • crates/mdk-macros/Cargo.toml
  • crates/mdk-core/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • Cargo.toml

Comment thread crates/mdk-core/src/groups.rs
Comment thread crates/mdk-macros/src/lib.rs
Comment thread crates/mdk-macros/src/lib.rs
@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.64% (+2.13%)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
crates/mdk-sqlite-storage/src/db.rs (3)

227-227: Simplify closure to method reference.

The closure |s| UnsignedEvent::from_json(s) can be replaced with the method directly.

♻️ Suggested diff
-    let event = text_to_parsed(row, "event", |s| UnsignedEvent::from_json(s))?;
+    let event = text_to_parsed(row, "event", UnsignedEvent::from_json)?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-sqlite-storage/src/db.rs` at line 227, Replace the inline closure
passed to text_to_parsed with the method reference: instead of
text_to_parsed(row, "event", |s| UnsignedEvent::from_json(s))?, pass
text_to_parsed(row, "event", UnsignedEvent::from_json)? so the call uses the
function pointer to UnsignedEvent::from_json directly when constructing event
from row.

280-280: Same simplification applies here.

♻️ Suggested diff
-    let event = text_to_parsed(row, "event", |s| UnsignedEvent::from_json(s))?;
+    let event = text_to_parsed(row, "event", UnsignedEvent::from_json)?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-sqlite-storage/src/db.rs` at line 280, Replace the trivial closure
passed to text_to_parsed with the function pointer directly: change the call
that creates event from text_to_parsed(row, "event", |s|
UnsignedEvent::from_json(s)) to use UnsignedEvent::from_json as the parser
argument so text_to_parsed(row, "event", UnsignedEvent::from_json) is used; this
removes the unnecessary closure while keeping the same behavior (referencing
text_to_parsed, UnsignedEvent::from_json, row and the "event" column).

19-63: Prefer Self over the type name in impl blocks.

Per coding guidelines, use Self instead of the type name when possible. Within macro-generated impl blocks, Self resolves correctly and improves consistency.

♻️ Suggested diff
         impl From<[u8; $len]> for $name {
             fn from(arr: [u8; $len]) -> Self {
-                $name(arr)
+                Self(arr)
             }
         }
                         if blob.len() == $len {
                             let mut arr = [0u8; $len];
                             arr.copy_from_slice(blob);
-                            Ok($name(arr))
+                            Ok(Self(arr))
                         } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-sqlite-storage/src/db.rs` around lines 19 - 63, In the
sqlite_blob_newtype macro, replace explicit uses of the generated type name
($name) with Self where allowed inside impl blocks: return Self(arr) in impl
From<[u8; $len]> for $name, use Self(arr) in the FromSql::column_result Ok path,
and use Self where the impl's return type denotes the concrete type (e.g., fn
from(val: $name) -> Self in impl From<$name> for [u8; $len]). Keep trait impl
headers unchanged but prefer Self for constructors and return types inside the
impl bodies to follow the coding guideline.
crates/mdk-storage-traits/src/welcomes/types.rs (1)

62-84: Restore direct coverage for every enum mapping.

These literals are part of the stored/wire format, but the remaining test in this file only exercises "processed". Failed, every WelcomeState variant, and the invalid parse paths are now uncovered, so a typo in one mapping would slip through.

🧪 Example test shape
@@
 #[cfg(test)]
 mod tests {
+    use std::str::FromStr;
     use serde_json::json;
@@
     fn test_processed_welcome_serialization() {
@@
     }
+
+    #[test]
+    fn test_welcome_state_enum_round_trips() {
+        let processed_cases = [
+            (ProcessedWelcomeState::Processed, "processed"),
+            (ProcessedWelcomeState::Failed, "failed"),
+        ];
+        for (state, expected) in processed_cases {
+            assert_eq!(state.to_string(), expected);
+            assert_eq!(serde_json::to_string(&state).unwrap(), format!("\"{expected}\""));
+            assert_eq!(ProcessedWelcomeState::from_str(expected).unwrap(), state);
+        }
+
+        let welcome_cases = [
+            (WelcomeState::Pending, "pending"),
+            (WelcomeState::Accepted, "accepted"),
+            (WelcomeState::Declined, "declined"),
+            (WelcomeState::Ignored, "ignored"),
+        ];
+        for (state, expected) in welcome_cases {
+            assert_eq!(state.to_string(), expected);
+            assert_eq!(serde_json::to_string(&state).unwrap(), format!("\"{expected}\""));
+            assert_eq!(WelcomeState::from_str(expected).unwrap(), state);
+        }
+    }
+
+    #[test]
+    fn test_welcome_state_enum_invalid_values_are_rejected() {
+        assert!(ProcessedWelcomeState::from_str("invalid").is_err());
+        assert!(WelcomeState::from_str("invalid").is_err());
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-storage-traits/src/welcomes/types.rs` around lines 62 - 84, Add
comprehensive unit tests that directly exercise every enum string mapping and
invalid parse paths for ProcessedWelcomeState and WelcomeState: assert that
ProcessedWelcomeState::Processed and ::Failed round-trip to/from their string
literals ("processed", "failed"), assert that each WelcomeState variant
(Pending, Accepted, Declined, Ignored) round-trips to/from their respective
literals, and add negative tests that attempt to parse invalid strings to ensure
the error path (WelcomeError) triggers; locate the enums ProcessedWelcomeState
and WelcomeState in types.rs to wire these assertions to the exact variants and
parsing functions used by string_enum!.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/mdk-storage-traits/src/welcomes/types.rs`:
- Around line 62-84: The generated error messages currently echo the invalid
input via the string_enum! macro (used for ProcessedWelcomeState and
WelcomeState), so replace the format-style invalid_message strings that include
"{}" with fixed messages that do not interpolate the invalid value (e.g. change
"Invalid processed welcome state: {}" to "Invalid processed welcome state" and
"Invalid welcome state: {}" to "Invalid welcome state") so deserialization
failures no longer reflect raw input into WelcomeError.

---

Nitpick comments:
In `@crates/mdk-sqlite-storage/src/db.rs`:
- Line 227: Replace the inline closure passed to text_to_parsed with the method
reference: instead of text_to_parsed(row, "event", |s|
UnsignedEvent::from_json(s))?, pass text_to_parsed(row, "event",
UnsignedEvent::from_json)? so the call uses the function pointer to
UnsignedEvent::from_json directly when constructing event from row.
- Line 280: Replace the trivial closure passed to text_to_parsed with the
function pointer directly: change the call that creates event from
text_to_parsed(row, "event", |s| UnsignedEvent::from_json(s)) to use
UnsignedEvent::from_json as the parser argument so text_to_parsed(row, "event",
UnsignedEvent::from_json) is used; this removes the unnecessary closure while
keeping the same behavior (referencing text_to_parsed, UnsignedEvent::from_json,
row and the "event" column).
- Around line 19-63: In the sqlite_blob_newtype macro, replace explicit uses of
the generated type name ($name) with Self where allowed inside impl blocks:
return Self(arr) in impl From<[u8; $len]> for $name, use Self(arr) in the
FromSql::column_result Ok path, and use Self where the impl's return type
denotes the concrete type (e.g., fn from(val: $name) -> Self in impl From<$name>
for [u8; $len]). Keep trait impl headers unchanged but prefer Self for
constructors and return types inside the impl bodies to follow the coding
guideline.

In `@crates/mdk-storage-traits/src/welcomes/types.rs`:
- Around line 62-84: Add comprehensive unit tests that directly exercise every
enum string mapping and invalid parse paths for ProcessedWelcomeState and
WelcomeState: assert that ProcessedWelcomeState::Processed and ::Failed
round-trip to/from their string literals ("processed", "failed"), assert that
each WelcomeState variant (Pending, Accepted, Declined, Ignored) round-trips
to/from their respective literals, and add negative tests that attempt to parse
invalid strings to ensure the error path (WelcomeError) triggers; locate the
enums ProcessedWelcomeState and WelcomeState in types.rs to wire these
assertions to the exact variants and parsing functions used by string_enum!.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 07d546ca-9f96-40f3-b188-21c75c7d02f0

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4651e and 0e9cfd9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • crates/mdk-core/src/key_packages.rs
  • crates/mdk-sqlite-storage/src/db.rs
  • crates/mdk-sqlite-storage/src/mls_storage/mod.rs
  • crates/mdk-storage-traits/Cargo.toml
  • crates/mdk-storage-traits/src/groups/types.rs
  • crates/mdk-storage-traits/src/lib.rs
  • crates/mdk-storage-traits/src/messages/types.rs
  • crates/mdk-storage-traits/src/welcomes/types.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/mdk-storage-traits/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/mdk-storage-traits/src/messages/types.rs
  • crates/mdk-storage-traits/src/groups/types.rs
  • crates/mdk-storage-traits/src/lib.rs

Comment thread crates/mdk-storage-traits/src/welcomes/types.rs
@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.62% (+2.11%)

@dannym-arx dannym-arx changed the title Clean Code Refactor codebase to reduce duplication Mar 30, 2026
@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.51% → 93.74% (+2.23%)

@dannym-arx
Copy link
Copy Markdown
Contributor Author

dannym-arx commented Mar 30, 2026

Only 2488 effective lines reduced, with I could have done more given that I've personally introduced more lines than that in a single pr lol

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.57% → 93.72% (+2.15%)

@github-actions
Copy link
Copy Markdown

✅ Coverage: 91.57% → 93.72% (+2.15%)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
crates/mdk-sqlite-storage/src/db.rs (1)

297-314: ⚠️ Potential issue | 🟡 Minor

Reject oversized member_count values instead of truncating them.

Line 314 narrows a u64 with as u32, so a malformed row above u32::MAX silently wraps. Convert with u32::try_from and return a conversion error instead.

🔎 Safe integer conversion
-    let member_count: u64 = row.get("member_count")?;
+    let member_count = u32::try_from(row.get::<_, u64>("member_count")?).map_err(|_| {
+        Error::FromSqlConversionFailure(
+            0,
+            Type::Integer,
+            Box::new(IoError::new(ErrorKind::InvalidData, "Invalid member count")),
+        )
+    })?;
@@
-        member_count: member_count as u32,
+        member_count,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-sqlite-storage/src/db.rs` around lines 297 - 314, The code
currently casts member_count: member_count as u32 which silently truncates
values > u32::MAX; instead perform a checked conversion using
u32::try_from(member_count) and return an error on failure so oversized values
are rejected. Update the construction of Welcome (the member_count field) to
call u32::try_from(member_count) and propagate a conversion error (matching the
function's error type) rather than using a plain as cast; this touches the same
function that reads the row (where member_count is obtained) alongside
text_to_state and blob_to_event_id.
crates/mdk-memory-storage/src/groups.rs (1)

159-163: ⚠️ Potential issue | 🔴 Critical

Clamp before adding offset + limit.

Line 161 can overflow on large offsets (for example usize::MAX), which turns this pagination path into a panic via arithmetic overflow or an invalid slice range. Use a saturating add from the clamped start index.

🛡️ Safe pagination bounds
-                let start = offset.min(messages.len());
-                let end = (offset + limit).min(messages.len());
+                let start = offset.min(messages.len());
+                let end = start.saturating_add(limit).min(messages.len());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-memory-storage/src/groups.rs` around lines 159 - 163, The
pagination currently computes end as (offset + limit).min(messages.len()) which
can overflow when offset is large; change the computation to base end off the
already-clamped start: compute start = offset.min(messages.len()) and then end =
start.saturating_add(limit).min(messages.len()), and return
messages[start..end].to_vec(); update the pagination code in the function where
start, offset, limit and messages are used to use saturating_add on start.
🧹 Nitpick comments (3)
crates/mdk-storage-traits/src/groups/mod.rs (1)

122-149: Add a shared regression test for legacy-group-event.

The new macro now routes a third exporter-secret namespace, but the shared backend tests in crates/mdk-core/tests/shared/group_tests.rs only exercise group-event and encrypted-media. A label typo or cache mix-up in the legacy path would compile and still pass CI.

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

In `@crates/mdk-storage-traits/src/groups/mod.rs` around lines 122 - 149, Add a
shared regression test that exercises the new "legacy-group-event"
exporter-secret namespace to ensure the macros route to the correct cache;
specifically, extend the existing shared backend group tests to call
get_group_legacy_exporter_secret and save_group_legacy_exporter_secret and
verify they read/write to the group_legacy_exporter_secrets_cache using the
label "legacy-group-event" (similar to existing tests for "group-event" and
"encrypted-media") to catch any label typos or cache mix-ups.
crates/mdk-sqlite-storage/src/groups.rs (1)

79-89: Avoid deserializing the full group for an existence guard.

find_group_by_mls_group_id() reads and decodes the entire groups row just to answer a yes/no question, so every guarded path now pays an extra row decode and can fail on unrelated row-shape issues. A SELECT EXISTS(...) check keeps this cheap and isolated.

Suggested change
 #[inline]
 fn ensure_group_exists(
     storage: &MdkSqliteStorage,
     mls_group_id: &GroupId,
 ) -> Result<(), GroupError> {
-    if storage.find_group_by_mls_group_id(mls_group_id)?.is_some() {
-        Ok(())
-    } else {
-        Err(group_not_found())
-    }
+    let exists = storage.with_connection(|conn| {
+        conn.query_row(
+            "SELECT EXISTS(SELECT 1 FROM groups WHERE mls_group_id = ?)",
+            params![mls_group_id.as_slice()],
+            |row| row.get::<_, bool>(0),
+        )
+        .map_err(into_group_err)
+    })?;
+
+    if exists {
+        Ok(())
+    } else {
+        Err(group_not_found())
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-sqlite-storage/src/groups.rs` around lines 79 - 89, The
ensure_group_exists function currently calls storage.find_group_by_mls_group_id
which reads and deserializes the entire groups row; change this to perform a
lightweight existence check (e.g., a SELECT EXISTS query) so we don't decode the
row. Implement or call a new/existing helper like
storage.exists_group_by_mls_group_id(mls_group_id) or run a direct SELECT
EXISTS(...) against the same database connection inside ensure_group_exists,
return Ok(()) when true and Err(group_not_found()) when false, and keep the
function signature and error behavior unchanged.
crates/mdk-core/tests/shared/mod.rs (1)

24-186: Use $crate-qualified paths inside the exported macro.

shared::... is resolved at the invocation site, so this harness only works when the caller happens to have a shared module in scope. $crate::shared::... makes the macro hygienic and keeps future wrappers or nested test modules from breaking.

Suggested change
-            shared::group_tests::test_save_and_find_group(storage);
+            $crate::shared::group_tests::test_save_and_find_group(storage);

Apply the same rewrite to the other shared::... calls in this macro.

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

In `@crates/mdk-core/tests/shared/mod.rs` around lines 24 - 186, The exported
macro storage_backend_tests currently uses bare paths like
shared::group_tests::test_save_and_find_group which are resolved at the call
site; change all such occurrences to $crate::shared::... (e.g.
$crate::shared::group_tests::test_save_and_find_group) so the macro is hygienic;
update every invocation inside the macro (all shared::group_tests::...,
shared::message_tests::..., shared::welcome_tests::...) to use
$crate::shared::... instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/mdk-sqlite-storage/src/db.rs`:
- Around line 20-23: The blob wrapper macro sqlite_blob_newtype currently
derives Debug and Copy which exposes and makes key bytes trivially duplicable;
remove Debug and Copy from the derive list in sqlite_blob_newtype, add Zeroize
and ZeroizeOnDrop derives (from the zeroize crate) so raw bytes are zeroed when
dropped, keep Clone/PartialEq/Eq as needed but do NOT implement Copy, and ensure
any public uses of Hash32 (and places that produce image_key and
group_image_key) wrap the value in Secret<T> before exposing it outside internal
APIs so keys are not printable or clonable.

In `@crates/mdk-sqlite-storage/src/groups.rs`:
- Around line 185-190: The call to validate_group_fields currently passes
usize::MAX which effectively disables the admin-count check; replace usize::MAX
with the real admin limit constant (e.g., MAX_GROUP_ADMINS or whatever project
constant enforces max admins) so validate_group_fields can enforce
admin_pubkeys.len() > max_admins; ensure that constant is defined/imported and
keep the JSON byte-size validation as an additional check rather than a
substitute.

---

Outside diff comments:
In `@crates/mdk-memory-storage/src/groups.rs`:
- Around line 159-163: The pagination currently computes end as (offset +
limit).min(messages.len()) which can overflow when offset is large; change the
computation to base end off the already-clamped start: compute start =
offset.min(messages.len()) and then end =
start.saturating_add(limit).min(messages.len()), and return
messages[start..end].to_vec(); update the pagination code in the function where
start, offset, limit and messages are used to use saturating_add on start.

In `@crates/mdk-sqlite-storage/src/db.rs`:
- Around line 297-314: The code currently casts member_count: member_count as
u32 which silently truncates values > u32::MAX; instead perform a checked
conversion using u32::try_from(member_count) and return an error on failure so
oversized values are rejected. Update the construction of Welcome (the
member_count field) to call u32::try_from(member_count) and propagate a
conversion error (matching the function's error type) rather than using a plain
as cast; this touches the same function that reads the row (where member_count
is obtained) alongside text_to_state and blob_to_event_id.

---

Nitpick comments:
In `@crates/mdk-core/tests/shared/mod.rs`:
- Around line 24-186: The exported macro storage_backend_tests currently uses
bare paths like shared::group_tests::test_save_and_find_group which are resolved
at the call site; change all such occurrences to $crate::shared::... (e.g.
$crate::shared::group_tests::test_save_and_find_group) so the macro is hygienic;
update every invocation inside the macro (all shared::group_tests::...,
shared::message_tests::..., shared::welcome_tests::...) to use
$crate::shared::... instead.

In `@crates/mdk-sqlite-storage/src/groups.rs`:
- Around line 79-89: The ensure_group_exists function currently calls
storage.find_group_by_mls_group_id which reads and deserializes the entire
groups row; change this to perform a lightweight existence check (e.g., a SELECT
EXISTS query) so we don't decode the row. Implement or call a new/existing
helper like storage.exists_group_by_mls_group_id(mls_group_id) or run a direct
SELECT EXISTS(...) against the same database connection inside
ensure_group_exists, return Ok(()) when true and Err(group_not_found()) when
false, and keep the function signature and error behavior unchanged.

In `@crates/mdk-storage-traits/src/groups/mod.rs`:
- Around line 122-149: Add a shared regression test that exercises the new
"legacy-group-event" exporter-secret namespace to ensure the macros route to the
correct cache; specifically, extend the existing shared backend group tests to
call get_group_legacy_exporter_secret and save_group_legacy_exporter_secret and
verify they read/write to the group_legacy_exporter_secrets_cache using the
label "legacy-group-event" (similar to existing tests for "group-event" and
"encrypted-media") to catch any label typos or cache mix-ups.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5ebb32b7-3625-429c-8852-0622e3b845d6

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4651e and fd03e77.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • crates/mdk-core/src/key_packages.rs
  • crates/mdk-core/tests/shared/group_tests.rs
  • crates/mdk-core/tests/shared/message_tests.rs
  • crates/mdk-core/tests/shared/mod.rs
  • crates/mdk-core/tests/shared/welcome_tests.rs
  • crates/mdk-memory-storage/src/groups.rs
  • crates/mdk-sqlite-storage/src/db.rs
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-sqlite-storage/src/lib.rs
  • crates/mdk-sqlite-storage/src/mls_storage/mod.rs
  • crates/mdk-storage-traits/Cargo.toml
  • crates/mdk-storage-traits/src/groups/mod.rs
  • crates/mdk-storage-traits/src/groups/types.rs
  • crates/mdk-storage-traits/src/lib.rs
  • crates/mdk-storage-traits/src/messages/types.rs
  • crates/mdk-storage-traits/src/welcomes/types.rs
✅ Files skipped from review due to trivial changes (3)
  • crates/mdk-storage-traits/Cargo.toml
  • crates/mdk-core/tests/shared/welcome_tests.rs
  • crates/mdk-core/tests/shared/message_tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/mdk-storage-traits/src/welcomes/types.rs

Comment thread crates/mdk-sqlite-storage/src/db.rs
Comment thread crates/mdk-sqlite-storage/src/groups.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/mdk-macros/src/lib.rs (1)

10-21: Add compile-time coverage for the macro contract.

These examples are all ignore blocks, so they won't catch expansion drift on their own. If this crate doesn't already have a trybuild/UI suite, I'd add a small matrix for <direct> vs wrapped setters, renamed fields, and nested-Option getters.

Also applies to: 165-171, 305-311

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

In `@crates/mdk-macros/src/lib.rs` around lines 10 - 21, The doc examples for the
setters! macro are marked ignore so they won't detect expansion regressions; add
compile-time UI/trybuild tests that exercise the setters! macro across the
matrix of variants (plain vs impl Into, <direct> vs wrapped setter, renamed
method -> field forms, and nested Option getter behavior) and include positive
and negative cases (expected expansions or errors) to catch expansion drift;
create trybuild test cases named to reflect each scenario and add them to the
crate's test suite so running cargo test/trybuild will verify the macro contract
for setters! (and repeat the same test matrix for the other example blocks
referenced).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/mdk-macros/src/lib.rs`:
- Around line 10-21: The doc examples for the setters! macro are marked ignore
so they won't detect expansion regressions; add compile-time UI/trybuild tests
that exercise the setters! macro across the matrix of variants (plain vs impl
Into, <direct> vs wrapped setter, renamed method -> field forms, and nested
Option getter behavior) and include positive and negative cases (expected
expansions or errors) to catch expansion drift; create trybuild test cases named
to reflect each scenario and add them to the crate's test suite so running cargo
test/trybuild will verify the macro contract for setters! (and repeat the same
test matrix for the other example blocks referenced).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d0a7a985-1558-42df-b31f-ea1a834deb7c

📥 Commits

Reviewing files that changed from the base of the PR and between fd03e77 and de33a01.

📒 Files selected for processing (2)
  • crates/mdk-macros/src/lib.rs
  • crates/mdk-sqlite-storage/src/db.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/mdk-sqlite-storage/src/db.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Mar 30, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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)
crates/mdk-sqlite-storage/src/groups.rs (1)

346-352: ⚠️ Potential issue | 🟠 Major

Reapply relay-set validation before mutating the table.

replace_group_relays() now normalizes the missing-group case, but it still deletes and reinserts whatever it is given without running the shared relay validator. That lets SQLite accept oversized relay sets or overlong URLs that the shared groups::validation layer was introduced to reject, so backend behavior can diverge from memory storage. Please call validate_relay_set(...) before opening the savepoint.

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

In `@crates/mdk-sqlite-storage/src/groups.rs` around lines 346 - 352, In
replace_group_relays, call the shared validator validate_relay_set(&relays) (or
the crate's equivalent) before mutating the DB: after ensure_group_exists(self,
group_id)? and before opening the savepoint/transaction and performing
delete/insert operations, return any validation error instead of proceeding;
this ensures oversized relay sets or overlong RelayUrl values are rejected
consistently with groups::validation.
🧹 Nitpick comments (1)
crates/mdk-memory-storage/src/groups.rs (1)

5-41: Consider moving use statements above macro definitions.

Per coding guidelines, all use statements should be placed at the TOP of their containing scope. Currently, the group_exporter_secret_get! and group_exporter_secret_save! macros (lines 5-33) are defined before the use statements (lines 35-41).

📦 Suggested reorder
 //! Memory-based storage implementation of the MdkStorageProvider trait for MDK groups

 use std::collections::BTreeSet;

+use mdk_storage_traits::GroupId;
+use mdk_storage_traits::groups::error::GroupError;
+use mdk_storage_traits::groups::types::*;
+use mdk_storage_traits::groups::validation::{validate_group_fields, validate_relay_set};
+use mdk_storage_traits::groups::{
+    GroupStorage, MessageSortOrder, Pagination, group_not_found, validate_message_limit,
+};
+use mdk_storage_traits::messages::types::Message;
+use nostr::{PublicKey, RelayUrl};
+
+use crate::MdkMemoryStorage;
+
 /// Implements `get_group_*_exporter_secret` for the memory backend.
 ...
-
-use mdk_storage_traits::GroupId;
-...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mdk-memory-storage/src/groups.rs` around lines 5 - 41, Move the
module-level use statements so they appear before the macro definitions: place
the lines importing GroupId, GroupError, the groups::types, validation
functions, and GroupStorage-related items above the macro_rules!
group_exporter_secret_get and macro_rules! group_exporter_secret_save
definitions; this keeps the imports at the top of the scope and preserves the
macro bodies (group_exporter_secret_get! and group_exporter_secret_save!)
unchanged except for their new position below the use declarations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/mdk-sqlite-storage/src/groups.rs`:
- Around line 346-352: In replace_group_relays, call the shared validator
validate_relay_set(&relays) (or the crate's equivalent) before mutating the DB:
after ensure_group_exists(self, group_id)? and before opening the
savepoint/transaction and performing delete/insert operations, return any
validation error instead of proceeding; this ensures oversized relay sets or
overlong RelayUrl values are rejected consistently with groups::validation.

---

Nitpick comments:
In `@crates/mdk-memory-storage/src/groups.rs`:
- Around line 5-41: Move the module-level use statements so they appear before
the macro definitions: place the lines importing GroupId, GroupError, the
groups::types, validation functions, and GroupStorage-related items above the
macro_rules! group_exporter_secret_get and macro_rules!
group_exporter_secret_save definitions; this keeps the imports at the top of the
scope and preserves the macro bodies (group_exporter_secret_get! and
group_exporter_secret_save!) unchanged except for their new position below the
use declarations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4425e3d0-7204-491d-b32c-3882e7029f7e

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4651e and de33a01.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • crates/mdk-core/src/key_packages.rs
  • crates/mdk-core/tests/shared/group_tests.rs
  • crates/mdk-core/tests/shared/message_tests.rs
  • crates/mdk-core/tests/shared/mod.rs
  • crates/mdk-core/tests/shared/welcome_tests.rs
  • crates/mdk-macros/src/lib.rs
  • crates/mdk-memory-storage/src/groups.rs
  • crates/mdk-sqlite-storage/src/db.rs
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-sqlite-storage/src/lib.rs
  • crates/mdk-sqlite-storage/src/mls_storage/mod.rs
  • crates/mdk-storage-traits/Cargo.toml
  • crates/mdk-storage-traits/src/groups/mod.rs
  • crates/mdk-storage-traits/src/groups/types.rs
  • crates/mdk-storage-traits/src/lib.rs
  • crates/mdk-storage-traits/src/messages/types.rs
  • crates/mdk-storage-traits/src/welcomes/types.rs
✅ Files skipped from review due to trivial changes (4)
  • crates/mdk-core/tests/shared/group_tests.rs
  • crates/mdk-core/tests/shared/welcome_tests.rs
  • crates/mdk-storage-traits/Cargo.toml
  • crates/mdk-storage-traits/src/welcomes/types.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/mdk-macros/src/lib.rs
  • crates/mdk-storage-traits/src/messages/types.rs
  • crates/mdk-storage-traits/src/lib.rs
  • crates/mdk-storage-traits/src/groups/mod.rs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/mdk-sqlite-storage/src/lib.rs (1)

78-85: Move generic trait bounds to a where clause in db_error_fn!.

This helper currently uses an inline bound (T: std::error::Error), which diverges from the repo’s Rust style rule.

♻️ Suggested patch
 macro_rules! db_error_fn {
     ($fn_name:ident, $error_type:ty) => {
         #[inline]
-        fn $fn_name<T: std::error::Error>(e: T) -> $error_type {
+        fn $fn_name<T>(e: T) -> $error_type
+        where
+            T: std::error::Error,
+        {
             <$error_type>::DatabaseError(e.to_string())
         }
     };
 }

As per coding guidelines, “All trait bounds in where clauses, not inline”.

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

In `@crates/mdk-sqlite-storage/src/lib.rs` around lines 78 - 85, The macro
db_error_fn! currently generates functions with an inline generic bound `T:
std::error::Error`; change the generated function signature to move that bound
into a where clause (i.e., generate `fn $fn_name<T>(e: T) -> $error_type where
T: std::error::Error { ... }`) so all trait bounds follow the repo style; keep
the body returning `<$error_type>::DatabaseError(e.to_string())` and preserve
#[inline] and macro argument names ($fn_name, $error_type).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/mdk-sqlite-storage/src/lib.rs`:
- Around line 78-85: The macro db_error_fn! currently generates functions with
an inline generic bound `T: std::error::Error`; change the generated function
signature to move that bound into a where clause (i.e., generate `fn
$fn_name<T>(e: T) -> $error_type where T: std::error::Error { ... }`) so all
trait bounds follow the repo style; keep the body returning
`<$error_type>::DatabaseError(e.to_string())` and preserve #[inline] and macro
argument names ($fn_name, $error_type).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8640c292-3668-4bfe-a630-8b0ed27879a1

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4651e and de33a01.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • crates/mdk-core/src/key_packages.rs
  • crates/mdk-core/tests/shared/group_tests.rs
  • crates/mdk-core/tests/shared/message_tests.rs
  • crates/mdk-core/tests/shared/mod.rs
  • crates/mdk-core/tests/shared/welcome_tests.rs
  • crates/mdk-macros/src/lib.rs
  • crates/mdk-memory-storage/src/groups.rs
  • crates/mdk-sqlite-storage/src/db.rs
  • crates/mdk-sqlite-storage/src/groups.rs
  • crates/mdk-sqlite-storage/src/lib.rs
  • crates/mdk-sqlite-storage/src/mls_storage/mod.rs
  • crates/mdk-storage-traits/Cargo.toml
  • crates/mdk-storage-traits/src/groups/mod.rs
  • crates/mdk-storage-traits/src/groups/types.rs
  • crates/mdk-storage-traits/src/lib.rs
  • crates/mdk-storage-traits/src/messages/types.rs
  • crates/mdk-storage-traits/src/welcomes/types.rs
✅ Files skipped from review due to trivial changes (3)
  • crates/mdk-core/tests/shared/welcome_tests.rs
  • crates/mdk-storage-traits/Cargo.toml
  • crates/mdk-core/tests/shared/message_tests.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/mdk-core/src/key_packages.rs
  • crates/mdk-macros/src/lib.rs
  • crates/mdk-core/tests/shared/mod.rs
  • crates/mdk-memory-storage/src/groups.rs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

✅ Coverage: 91.36% → 93.54% (+2.18%)

@dannym-arx dannym-arx merged commit a675b3d into master Apr 2, 2026
19 of 20 checks passed
@dannym-arx dannym-arx deleted the clean-up-code branch April 2, 2026 13:59
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.

2 participants