lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit e2c4871159ee4f025b8669df2a25e54a222ee662
parent 7080a146f6742df7da89df40b86b17d090804c97
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 04:04:26 -0700

workspace: close final refactor gates

- replace broad constructor and reducer argument lists with typed input structs
- align listing address vectors, coverage policy, and release-contract test resolution
- clean clippy findings across event, Nostr, SQL, runtime, replica, and tooling crates
- validate with cargo fmt/check/test/clippy, contract, release-preflight, and flake check

Diffstat:
Mcrates/authority/src/authorization.rs | 26+++++++++++++++-----------
Mcrates/authority/src/signer.rs | 22++++++++++++----------
Mcrates/events/src/draft.rs | 80+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mcrates/events_codec/src/app_data/decode.rs | 2+-
Mcrates/events_codec/src/app_data/encode.rs | 4+---
Mcrates/events_codec/src/calendar/encode.rs | 26+++++++++++++-------------
Mcrates/events_codec/src/comment/encode.rs | 16++++++++--------
Mcrates/events_codec/src/coop/decode.rs | 2+-
Mcrates/events_codec/src/coop/encode.rs | 5+----
Mcrates/events_codec/src/document/decode.rs | 14+++++++-------
Mcrates/events_codec/src/document/encode.rs | 5+----
Mcrates/events_codec/src/event_ref.rs | 22+++++++++-------------
Mcrates/events_codec/src/farm/decode.rs | 2+-
Mcrates/events_codec/src/farm/encode.rs | 5+----
Mcrates/events_codec/src/farm_crdt/decode.rs | 11+++++------
Mcrates/events_codec/src/farm_workspace/decode.rs | 8++++----
Mcrates/events_codec/src/field_helpers.rs | 8++++----
Mcrates/events_codec/src/file_metadata/encode.rs | 8++++----
Mcrates/events_codec/src/follow/decode.rs | 2+-
Mcrates/events_codec/src/geochat/decode.rs | 6+++---
Mcrates/events_codec/src/gift_wrap/decode.rs | 4++--
Mcrates/events_codec/src/job/encode.rs | 2+-
Mcrates/events_codec/src/job/feedback/decode.rs | 8++++----
Mcrates/events_codec/src/job/request/decode.rs | 8++++----
Mcrates/events_codec/src/job/result/decode.rs | 8++++----
Mcrates/events_codec/src/job/util.rs | 12++++++------
Mcrates/events_codec/src/list/decode.rs | 9+++++----
Mcrates/events_codec/src/list/encode.rs | 11++++++-----
Mcrates/events_codec/src/list_set/encode.rs | 7++-----
Mcrates/events_codec/src/listing/decode.rs | 16++++++++--------
Mcrates/events_codec/src/listing/tags.rs | 119+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mcrates/events_codec/src/message/tags.rs | 14+++++++-------
Mcrates/events_codec/src/message_file/decode.rs | 10+++++-----
Mcrates/events_codec/src/order/encode.rs | 212+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mcrates/events_codec/src/order/tags.rs | 5+----
Mcrates/events_codec/src/plot/decode.rs | 6+++---
Mcrates/events_codec/src/plot/encode.rs | 5+----
Mcrates/events_codec/src/post/encode.rs | 24++++++++++++------------
Mcrates/events_codec/src/profile/decode.rs | 2+-
Mcrates/events_codec/src/profile/encode.rs | 5+----
Mcrates/events_codec/src/reaction/encode.rs | 16++++++++--------
Mcrates/events_codec/src/resource_area/decode.rs | 2+-
Mcrates/events_codec/src/resource_area/encode.rs | 5+----
Mcrates/events_codec/src/resource_cap/decode.rs | 2+-
Mcrates/events_codec/src/resource_cap/encode.rs | 13+++++--------
Mcrates/events_codec/src/social_helpers.rs | 16++++++++--------
Mcrates/geocoder/src/geocoder.rs | 5+----
Mcrates/identity/src/identity.rs | 4++--
Mcrates/identity/src/username.rs | 2+-
Mcrates/log/src/lib.rs | 16+++-------------
Mcrates/net/build.rs | 11+++++------
Mcrates/net/src/builder.rs | 13++-----------
Mcrates/net/src/keys.rs | 38+++++++++++++++++---------------------
Mcrates/net/src/net.rs | 4++--
Mcrates/net/src/nostr_client/manager.rs | 8++++----
Mcrates/nostr/src/client.rs | 2+-
Mcrates/nostr/src/error.rs | 2+-
Mcrates/nostr/src/event_adapters.rs | 2+-
Mcrates/nostr/src/events/application_handler.rs | 16++++++++--------
Mcrates/nostr/src/events/metadata.rs | 4++--
Mcrates/nostr/src/events/post.rs | 11+++++------
Mcrates/nostr/src/identity_profile.rs | 7++-----
Mcrates/nostr/src/nip17.rs | 4++--
Mcrates/nostr/src/tags.rs | 13+++++++------
Mcrates/nostr_accounts/src/manager.rs | 8++++----
Mcrates/nostr_connect/src/message.rs | 8++++----
Mcrates/nostr_ndb/src/ingest.rs | 13+++++--------
Mcrates/nostr_runtime/src/runtime.rs | 34+++++++++++++++++-----------------
Mcrates/nostr_signer/src/backend.rs | 25++++++++++++++++---------
Mcrates/nostr_signer/src/capability.rs | 39++++++++++++++++++++++-----------------
Mcrates/nostr_signer/src/evaluation.rs | 4++--
Mcrates/nostr_signer/src/manager.rs | 45+++++++++++++++++++++------------------------
Mcrates/nostr_signer/src/model.rs | 9++-------
Mcrates/nostr_signer/src/nip46.rs | 63++++++++++++++++++++++++++++++++-------------------------------
Mcrates/nostr_signer/src/store.rs | 4++--
Mcrates/outbox/src/store.rs | 37++++++++++++++++++-------------------
Mcrates/replica_db/src/backup.rs | 2+-
Mcrates/replica_db_schema/src/lib.rs | 2+-
Mcrates/replica_db_schema/src/models/gcs_location.rs | 2+-
Mcrates/replica_db_schema/src/models/nostr_profile.rs | 2+-
Mcrates/replica_db_schema/src/models/nostr_relay.rs | 2+-
Mcrates/replica_sync/src/emit.rs | 27++++++++++++++-------------
Mcrates/replica_sync/src/error.rs | 2+-
Mcrates/replica_sync/src/event_head.rs | 2+-
Mcrates/replica_sync/src/ingest.rs | 30++++++++++++++----------------
Mcrates/runtime/src/error.rs | 8+++++++-
Mcrates/runtime_manager/src/managed.rs | 6+++---
Mcrates/secret_vault/src/selection.rs | 8++++----
Mcrates/simplex_agent_store/src/store.rs | 22+++++-----------------
Mcrates/simplex_chat_proto/src/codec.rs | 8++++----
Mcrates/simplex_chat_proto/src/model.rs | 4++--
Mcrates/simplex_smp_crypto/src/auth.rs | 4+++-
Mcrates/simplex_smp_crypto/src/message.rs | 21+++++++++------------
Mcrates/simplex_smp_proto/src/wire.rs | 17++++++++---------
Mcrates/simplex_smp_transport/src/client.rs | 100++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mcrates/sp1_host_trade/src/lib.rs | 16+++++++---------
Mcrates/sql_core/src/executor_embedded.rs | 6++++++
Mcrates/sql_core/src/executor_sqlite.rs | 9+++------
Mcrates/sql_core/src/utils.rs | 5+----
Mcrates/sql_wasm_bridge/src/lib.rs | 14++------------
Mcrates/trade/src/listing/codec.rs | 28++++++++++++++--------------
Mcrates/trade/src/listing/validation.rs | 1-
Mcrates/trade/src/order.rs | 405+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mcrates/trade/src/validation_receipt.rs | 96++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mcrates/xtask/src/contract.rs | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mcrates/xtask/src/coverage.rs | 101++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mcrates/xtask/src/phase1_1.rs | 3+--
Mpolicy/coverage/POLICY.md | 3++-
Mpolicy/coverage/policy.toml | 18++++++++++++++++++
Mspec/RCLD.md | 2+-
Mspec/README.md | 6++++--
Mspec/conformance/vectors/order/build_order_decision_draft.v1.json | 10+++++-----
Mspec/conformance/vectors/order/build_order_request_draft.v1.json | 8++++----
Mspec/conformance/vectors/order/parse_listing_address.v1.json | 2+-
Mspec/conformance/vectors/order/parse_order_decision.v1.json | 2+-
Mspec/conformance/vectors/order/parse_order_request.v1.json | 2+-
Mspec/operations.toml | 6++----
117 files changed, 1279 insertions(+), 1110 deletions(-)

diff --git a/crates/authority/src/authorization.rs b/crates/authority/src/authorization.rs @@ -2,6 +2,8 @@ use crate::{RadrootsActorContext, RadrootsAuthorityError, RadrootsEventSigner}; use radroots_events::contract::{RadrootsEventContract, event_contract}; +#[cfg(test)] +use radroots_events::draft::RadrootsSignedNostrEventParts; use radroots_events::draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent}; #[cfg(not(feature = "std"))] @@ -158,18 +160,20 @@ mod tests { &self, draft: &RadrootsFrozenEventDraft, ) -> Result<RadrootsSignedNostrEvent, RadrootsSignerError> { - RadrootsSignedNostrEvent::new( - self.event_id + RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts { + id: self + .event_id .as_deref() - .unwrap_or(draft.expected_event_id.as_str()), - self.pubkey.as_str(), - draft.created_at, - draft.kind, - draft.tags.clone(), - draft.content.as_str(), - hex_128('f'), - "{}", - ) + .unwrap_or(draft.expected_event_id.as_str()) + .to_owned(), + pubkey: self.pubkey.to_string(), + created_at: draft.created_at, + kind: draft.kind, + tags: draft.tags.clone(), + content: draft.content.clone(), + sig: hex_128('f'), + raw_json: "{}".to_owned(), + }) .map_err(|error| RadrootsSignerError::SigningFailed { message: error.to_string(), }) diff --git a/crates/authority/src/signer.rs b/crates/authority/src/signer.rs @@ -1,6 +1,8 @@ #![forbid(unsafe_code)] use crate::{RadrootsAuthorityError, RadrootsSignerError}; +#[cfg(test)] +use radroots_events::draft::RadrootsSignedNostrEventParts; use radroots_events::draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent}; use radroots_events::ids::RadrootsPublicKey; @@ -96,16 +98,16 @@ mod tests { } }); } - RadrootsSignedNostrEvent::new( - draft.expected_event_id.as_str(), - self.pubkey.as_str(), - draft.created_at, - draft.kind, - draft.tags.clone(), - draft.content.as_str(), - hex_128('f'), - "{}", - ) + RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts { + id: draft.expected_event_id.to_string(), + pubkey: self.pubkey.to_string(), + created_at: draft.created_at, + kind: draft.kind, + tags: draft.tags.clone(), + content: draft.content.clone(), + sig: hex_128('f'), + raw_json: "{}".to_owned(), + }) .map_err(|error| RadrootsSignerError::SigningFailed { message: error.to_string(), }) diff --git a/crates/events/src/draft.rs b/crates/events/src/draft.rs @@ -131,6 +131,19 @@ impl RadrootsFrozenEventDraft { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsSignedNostrEventParts { + pub id: String, + pub pubkey: String, + pub created_at: u32, + pub kind: u32, + pub tags: Vec<Vec<String>>, + pub content: String, + pub sig: String, + pub raw_json: String, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsSignedNostrEvent { pub id: String, pub pubkey: String, @@ -143,28 +156,19 @@ pub struct RadrootsSignedNostrEvent { } impl RadrootsSignedNostrEvent { - pub fn new( - id: impl AsRef<str>, - pubkey: impl AsRef<str>, - created_at: u32, - kind: u32, - tags: Vec<Vec<String>>, - content: impl Into<String>, - sig: impl AsRef<str>, - raw_json: impl Into<String>, - ) -> Result<Self, RadrootsDraftError> { - let id = RadrootsEventId::parse(id.as_ref())?.into_string(); - let pubkey = RadrootsPublicKey::parse(pubkey.as_ref())?.into_string(); - let sig = RadrootsEventSignature::parse(sig.as_ref())?.into_string(); + pub fn new(parts: RadrootsSignedNostrEventParts) -> Result<Self, RadrootsDraftError> { + let id = RadrootsEventId::parse(parts.id)?.into_string(); + let pubkey = RadrootsPublicKey::parse(parts.pubkey)?.into_string(); + let sig = RadrootsEventSignature::parse(parts.sig)?.into_string(); Ok(Self { id, pubkey, - created_at, - kind, - tags, - content: content.into(), + created_at: parts.created_at, + kind: parts.kind, + tags: parts.tags, + content: parts.content, sig, - raw_json: raw_json.into(), + raw_json: parts.raw_json, }) } @@ -172,16 +176,16 @@ impl RadrootsSignedNostrEvent { event: RadrootsNostrEvent, raw_json: impl Into<String>, ) -> Result<Self, RadrootsDraftError> { - Self::new( - event.id, - event.author, - event.created_at, - event.kind, - event.tags, - event.content, - event.sig, - raw_json, - ) + Self::new(RadrootsSignedNostrEventParts { + id: event.id, + pubkey: event.author, + created_at: event.created_at, + kind: event.kind, + tags: event.tags, + content: event.content, + sig: event.sig, + raw_json: raw_json.into(), + }) } } @@ -343,16 +347,16 @@ mod tests { #[test] fn signed_event_validates_ids_and_roundtrips_with_serde() { - let signed = RadrootsSignedNostrEvent::new( - hex_64('d'), - hex_64('e'), - 10, - KIND_POST, - Vec::new(), - "hello", - "f".repeat(128), - "{\"id\":\"fixture\"}", - ) + let signed = RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts { + id: hex_64('d'), + pubkey: hex_64('e'), + created_at: 10, + kind: KIND_POST, + tags: Vec::new(), + content: "hello".to_owned(), + sig: "f".repeat(128), + raw_json: "{\"id\":\"fixture\"}".to_owned(), + }) .expect("signed event"); let json = serde_json::to_string(&signed).expect("serialize"); let decoded: RadrootsSignedNostrEvent = serde_json::from_str(&json).expect("deserialize"); diff --git a/crates/events_codec/src/app_data/decode.rs b/crates/events_codec/src/app_data/decode.rs @@ -16,7 +16,7 @@ use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent}; fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or(EventParseError::MissingTag(TAG_D))?; let value = tag .get(1) diff --git a/crates/events_codec/src/app_data/encode.rs b/crates/events_codec/src/app_data/encode.rs @@ -17,9 +17,7 @@ pub fn app_data_build_tags( if app_data.d_tag.trim().is_empty() { return Err(EventEncodeError::EmptyRequiredField("d_tag")); } - let mut tags = Vec::with_capacity(1); - tags.push(vec![TAG_D.to_string(), app_data.d_tag.clone()]); - Ok(tags) + Ok(vec![vec![TAG_D.to_string(), app_data.d_tag.clone()]]) } pub fn to_wire_parts(app_data: &RadrootsAppData) -> Result<WireEventParts, EventEncodeError> { diff --git a/crates/events_codec/src/calendar/encode.rs b/crates/events_codec/src/calendar/encode.rs @@ -104,15 +104,15 @@ pub fn rsvp_build_tags( push_calendar_event_address(&mut tags, &rsvp.event, "event")?; if let Some(event_id) = rsvp.event_id.as_deref() { let mut tag = vec![TAG_E.to_string(), event_id.to_string()]; - if let RadrootsSocialTarget::Address { relays, .. } = &rsvp.event { - if let Some(relays) = relays.as_ref() { - tag.extend( - relays - .iter() - .filter(|relay| !relay.trim().is_empty()) - .cloned(), - ); - } + if let RadrootsSocialTarget::Address { relays, .. } = &rsvp.event + && let Some(relays) = relays.as_ref() + { + tag.extend( + relays + .iter() + .filter(|relay| !relay.trim().is_empty()) + .cloned(), + ); } tags.push(tag); } @@ -265,10 +265,10 @@ fn push_calendar_event_address( if !is_calendar_event_kind(address.kind) { return Err(EventEncodeError::InvalidField(field)); } - if let Some(event_kind) = event_kind { - if *event_kind != address.kind { - return Err(EventEncodeError::InvalidField(field)); - } + if let Some(event_kind) = event_kind + && *event_kind != address.kind + { + return Err(EventEncodeError::InvalidField(field)); } let value = format!("{}:{}:{}", address.kind, address.pubkey, address.d_tag); if let Some(relays) = relays.as_ref() { diff --git a/crates/events_codec/src/comment/encode.rs b/crates/events_codec/src/comment/encode.rs @@ -119,15 +119,15 @@ fn push_comment_target( let parsed = parse_address_tag(address, keys.field) .map_err(|_| EventEncodeError::InvalidField(keys.field))?; validate_comment_target_kind(parsed.kind, keys.field)?; - if let Some(kind) = event_kind { - if *kind != parsed.kind { - return Err(EventEncodeError::InvalidField(keys.field)); - } + if let Some(kind) = event_kind + && *kind != parsed.kind + { + return Err(EventEncodeError::InvalidField(keys.field)); } - if let Some(author) = author.as_deref() { - if author != parsed.pubkey { - return Err(EventEncodeError::InvalidField(keys.field)); - } + if let Some(author) = author.as_deref() + && author != parsed.pubkey + { + return Err(EventEncodeError::InvalidField(keys.field)); } let mut address_tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len)); address_tag.push(keys.address.to_string()); diff --git a/crates/events_codec/src/coop/decode.rs b/crates/events_codec/src/coop/decode.rs @@ -18,7 +18,7 @@ const DEFAULT_KIND: u32 = KIND_COOP; fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or(EventParseError::MissingTag(TAG_D))?; let value = tag .get(1) diff --git a/crates/events_codec/src/coop/encode.rs b/crates/events_codec/src/coop/encode.rs @@ -22,10 +22,7 @@ const TAG_T: &str = "t"; const TAG_G: &str = "g"; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } pub fn coop_build_tags(coop: &RadrootsCoop) -> Result<Vec<Vec<String>>, EventEncodeError> { diff --git a/crates/events_codec/src/document/decode.rs b/crates/events_codec/src/document/decode.rs @@ -22,7 +22,7 @@ const DEFAULT_KIND: u32 = KIND_DOCUMENT; fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or(EventParseError::MissingTag(TAG_D))?; let value = tag .get(1) @@ -38,7 +38,7 @@ fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { fn parse_subject_pubkey(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_P)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_P)) .ok_or(EventParseError::MissingTag(TAG_P))?; let value = tag .get(1) @@ -53,7 +53,7 @@ fn parse_subject_pubkey(tags: &[Vec<String>]) -> Result<String, EventParseError> fn parse_subject_address(tags: &[Vec<String>]) -> Result<Option<String>, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_A)); + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_A)); let Some(tag) = tag else { return Ok(None) }; let value = tag .get(1) @@ -97,10 +97,10 @@ pub fn document_from_event( return Err(EventParseError::InvalidTag(TAG_P)); } - if let Some(address) = document.subject.address.as_ref() { - if address.trim().is_empty() { - return Err(EventParseError::InvalidTag(TAG_A)); - } + if let Some(address) = document.subject.address.as_ref() + && address.trim().is_empty() + { + return Err(EventParseError::InvalidTag(TAG_A)); } if let Some(tag_address) = subject_address { diff --git a/crates/events_codec/src/document/encode.rs b/crates/events_codec/src/document/encode.rs @@ -22,10 +22,7 @@ const TAG_P: &str = "p"; const TAG_A: &str = "a"; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } pub fn document_build_tags( diff --git a/crates/events_codec/src/event_ref.rs b/crates/events_codec/src/event_ref.rs @@ -30,7 +30,7 @@ pub fn parse_event_ref_tag( tag: &[String], tag_name: &'static str, ) -> Result<RadrootsNostrEventRef, EventParseError> { - if tag.get(0).map(|s| s.as_str()) != Some(tag_name) { + if tag.first().map(|s| s.as_str()) != Some(tag_name) { return Err(EventParseError::InvalidTag(tag_name)); } let id = tag.get(1).ok_or(EventParseError::InvalidTag(tag_name))?; @@ -67,7 +67,7 @@ pub fn find_event_ref_tag<'a>( tag_name: &'static str, ) -> Option<&'a Vec<String>> { tags.iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(tag_name)) + .find(|t| t.first().map(|s| s.as_str()) == Some(tag_name)) } pub fn push_nip10_ref_tags( @@ -89,14 +89,10 @@ pub fn push_nip10_ref_tags( } tags.push(e_tag); - let mut p_tag = Vec::with_capacity(2); - p_tag.push(tag_p.to_string()); - p_tag.push(event.author.clone()); + let p_tag = vec![tag_p.to_string(), event.author.clone()]; tags.push(p_tag); - let mut k_tag = Vec::with_capacity(2); - k_tag.push(tag_k.to_string()); - k_tag.push(kind_str.clone()); + let k_tag = vec![tag_k.to_string(), kind_str.clone()]; tags.push(k_tag); if let Some(d_tag) = event.d_tag.as_deref().filter(|v| !v.is_empty()) { @@ -151,7 +147,7 @@ pub fn parse_nip10_ref_tags( let mut addr_relays: Option<Vec<String>> = None; for tag in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some(tag_a)) + .filter(|t| t.first().map(|s| s.as_str()) == Some(tag_a)) { let value = match tag.get(1) { Some(v) => v, @@ -164,10 +160,10 @@ pub fn parse_nip10_ref_tags( if kind_part != Some(kind_key.as_str()) || author_part != Some(author.as_str()) { continue; } - if let Some(d) = d_part { - if !d.is_empty() { - d_tag = Some(d.to_string()); - } + if let Some(d) = d_part + && !d.is_empty() + { + d_tag = Some(d.to_string()); } if tag.len() > 2 { addr_relays = Some(tag[2..].to_vec()); diff --git a/crates/events_codec/src/farm/decode.rs b/crates/events_codec/src/farm/decode.rs @@ -17,7 +17,7 @@ const DEFAULT_KIND: u32 = KIND_FARM; fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or(EventParseError::MissingTag(TAG_D))?; let value = tag .get(1) diff --git a/crates/events_codec/src/farm/encode.rs b/crates/events_codec/src/farm/encode.rs @@ -20,10 +20,7 @@ const TAG_T: &str = "t"; const TAG_G: &str = "g"; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } pub fn farm_build_tags(farm: &RadrootsFarm) -> Result<Vec<Vec<String>>, EventEncodeError> { diff --git a/crates/events_codec/src/farm_crdt/decode.rs b/crates/events_codec/src/farm_crdt/decode.rs @@ -123,12 +123,11 @@ fn farm_crdt_change_from_event_inner( { return Err(EventParseError::MissingTag(TAG_T)); } - if let Some(tag_author) = optional_tag_value(tags, TAG_P)? { - if let Some(author_pubkey) = author_pubkey { - if tag_author != author_pubkey { - return Err(EventParseError::InvalidTag(TAG_P)); - } - } + if let Some(tag_author) = optional_tag_value(tags, TAG_P)? + && let Some(author_pubkey) = author_pubkey + && tag_author != author_pubkey + { + return Err(EventParseError::InvalidTag(TAG_P)); } let change: RadrootsFarmCrdtChange = diff --git a/crates/events_codec/src/farm_workspace/decode.rs b/crates/events_codec/src/farm_workspace/decode.rs @@ -55,10 +55,10 @@ pub fn farm_workspace_from_event( if manifest.farm_group_id != farm_group_id { return Err(EventParseError::InvalidTag(TAG_H)); } - if let Some(owner_pubkey) = optional_tag_value(tags, TAG_P)? { - if owner_pubkey != manifest.owner_pubkey { - return Err(EventParseError::InvalidTag(TAG_P)); - } + if let Some(owner_pubkey) = optional_tag_value(tags, TAG_P)? + && owner_pubkey != manifest.owner_pubkey + { + return Err(EventParseError::InvalidTag(TAG_P)); } let marker_tags = tag_values(tags, TAG_T)?; if !marker_tags diff --git a/crates/events_codec/src/field_helpers.rs b/crates/events_codec/src/field_helpers.rs @@ -149,10 +149,10 @@ pub(crate) fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: impl Into< } pub(crate) fn push_optional_tag(tags: &mut Vec<Vec<String>>, key: &str, value: Option<&str>) { - if let Some(value) = value { - if !value.trim().is_empty() { - push_tag(tags, key, value); - } + if let Some(value) = value + && !value.trim().is_empty() + { + push_tag(tags, key, value); } } diff --git a/crates/events_codec/src/file_metadata/encode.rs b/crates/events_codec/src/file_metadata/encode.rs @@ -86,10 +86,10 @@ fn validate_metadata(metadata: &RadrootsFileMetadata) -> Result<(), EventEncodeE if let Some(hash) = metadata.original_sha256.as_deref() { validate_lowercase_hex_64(hash, "original_sha256")?; } - if let Some(dimensions) = metadata.dimensions.as_ref() { - if dimensions.width == 0 || dimensions.height == 0 { - return Err(EventEncodeError::InvalidField("dimensions")); - } + if let Some(dimensions) = metadata.dimensions.as_ref() + && (dimensions.width == 0 || dimensions.height == 0) + { + return Err(EventEncodeError::InvalidField("dimensions")); } if let Some(thumbnails) = metadata.thumbnails.as_ref() { for thumbnail in thumbnails { diff --git a/crates/events_codec/src/follow/decode.rs b/crates/events_codec/src/follow/decode.rs @@ -59,7 +59,7 @@ pub fn follow_from_tags( let mut list = Vec::new(); for tag in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("p")) { list.push(parse_follow_tag(tag, published_at)?); } diff --git a/crates/events_codec/src/geochat/decode.rs b/crates/events_codec/src/geochat/decode.rs @@ -18,7 +18,7 @@ const TAG_T_TELEPORT: &str = "teleport"; fn parse_geohash_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_G)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_G)) .ok_or(EventParseError::MissingTag("g"))?; let geohash = tag.get(1).ok_or(EventParseError::InvalidTag("g"))?; if geohash.trim().is_empty() { @@ -30,7 +30,7 @@ fn parse_geohash_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { fn parse_nickname_tag(tags: &[Vec<String>]) -> Result<Option<String>, EventParseError> { let tag = match tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_N)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_N)) { Some(tag) => tag, None => return Ok(None), @@ -45,7 +45,7 @@ fn parse_nickname_tag(tags: &[Vec<String>]) -> Result<Option<String>, EventParse fn parse_teleport_tag(tags: &[Vec<String>]) -> Result<bool, EventParseError> { for tag in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_T)) + .filter(|t| t.first().map(|s| s.as_str()) == Some(TAG_T)) { let value = tag.get(1).ok_or(EventParseError::InvalidTag("t"))?; if value.trim().is_empty() { diff --git a/crates/events_codec/src/gift_wrap/decode.rs b/crates/events_codec/src/gift_wrap/decode.rs @@ -18,7 +18,7 @@ const DEFAULT_KIND: u32 = KIND_GIFT_WRAP; fn parse_recipient(tags: &[Vec<String>]) -> Result<RadrootsGiftWrapRecipient, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .find(|t| t.first().map(|s| s.as_str()) == Some("p")) .ok_or(EventParseError::MissingTag("p"))?; let public_key = tag.get(1).ok_or(EventParseError::InvalidTag("p"))?; if public_key.trim().is_empty() { @@ -38,7 +38,7 @@ fn parse_recipient(tags: &[Vec<String>]) -> Result<RadrootsGiftWrapRecipient, Ev fn parse_expiration(tags: &[Vec<String>]) -> Result<Option<u32>, EventParseError> { let value = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("expiration")) + .find(|t| t.first().map(|s| s.as_str()) == Some("expiration")) .and_then(|t| t.get(1)); let Some(value) = value else { return Ok(None); diff --git a/crates/events_codec/src/job/encode.rs b/crates/events_codec/src/job/encode.rs @@ -52,5 +52,5 @@ pub fn push_relay_tag(tags: &mut Vec<Vec<String>>, r: &str) { pub fn assert_no_inputs_when_encrypted(tags: &[Vec<String>]) -> bool { !tags .iter() - .any(|t| t.get(0).map(|s| s == "i").unwrap_or(false)) + .any(|t| t.first().map(|s| s == "i").unwrap_or(false)) } diff --git a/crates/events_codec/src/job/feedback/decode.rs b/crates/events_codec/src/job/feedback/decode.rs @@ -22,10 +22,10 @@ pub fn job_feedback_from_tags( ) -> Result<RadrootsJobFeedback, JobParseError> { let etag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("e")) + .find(|t| t.first().map(|s| s.as_str()) == Some("e")) .or_else(|| { tags.iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("e_ref")) + .find(|t| t.first().map(|s| s.as_str()) == Some("e_ref")) }) .ok_or(JobParseError::MissingTag("e"))?; let req_id = etag.get(1).ok_or(JobParseError::InvalidTag("e"))?.clone(); @@ -33,7 +33,7 @@ pub fn job_feedback_from_tags( let status_tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("status")) + .find(|t| t.first().map(|s| s.as_str()) == Some("status")) .ok_or(JobParseError::MissingTag("status"))?; let status = match status_tag.get(1).and_then(|s| feedback_status_from_tag(s)) { @@ -52,7 +52,7 @@ pub fn job_feedback_from_tags( let customer_pubkey = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .find(|t| t.first().map(|s| s.as_str()) == Some("p")) .and_then(|t| t.get(1).cloned()); Ok(RadrootsJobFeedback { diff --git a/crates/events_codec/src/job/request/decode.rs b/crates/events_codec/src/job/request/decode.rs @@ -21,7 +21,7 @@ pub fn job_request_from_tags( let output = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("output")) + .find(|t| t.first().map(|s| s.as_str()) == Some("output")) .and_then(|t| t.get(1).cloned()); let params: Vec<RadrootsJobParam> = parse_params(tags); @@ -30,19 +30,19 @@ pub fn job_request_from_tags( let relays = tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("relays")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("relays")) .filter_map(|t| t.get(1).cloned()) .collect::<Vec<_>>(); let providers = tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("p")) .filter_map(|t| t.get(1).cloned()) .collect::<Vec<_>>(); let topics = tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("t")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("t")) .filter_map(|t| t.get(1).cloned()) .collect::<Vec<_>>(); diff --git a/crates/events_codec/src/job/result/decode.rs b/crates/events_codec/src/job/result/decode.rs @@ -22,10 +22,10 @@ pub fn job_result_from_tags( ) -> Result<RadrootsJobResult, JobParseError> { let etag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("e")) + .find(|t| t.first().map(|s| s.as_str()) == Some("e")) .or_else(|| { tags.iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("e_ref")) + .find(|t| t.first().map(|s| s.as_str()) == Some("e_ref")) }) .ok_or(JobParseError::MissingTag("e"))?; @@ -34,7 +34,7 @@ pub fn job_result_from_tags( let request_json = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("request")) + .find(|t| t.first().map(|s| s.as_str()) == Some("request")) .and_then(|t| t.get(1).cloned()); let inputs: Vec<RadrootsJobInput> = parse_i_tags(tags); @@ -48,7 +48,7 @@ pub fn job_result_from_tags( let customer_pubkey = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .find(|t| t.first().map(|s| s.as_str()) == Some("p")) .and_then(|t| t.get(1).cloned()); Ok(RadrootsJobResult { diff --git a/crates/events_codec/src/job/util.rs b/crates/events_codec/src/job/util.rs @@ -39,7 +39,7 @@ fn looks_like_ws_relay(s: &str) -> bool { pub fn parse_bool_encrypted(tags: &[Vec<String>]) -> bool { tags.iter() - .any(|t| t.get(0).map(|s| s.as_str()) == Some("encrypted")) + .any(|t| t.first().map(|s| s.as_str()) == Some("encrypted")) } #[inline] @@ -90,7 +90,7 @@ pub fn parse_i_tags(tags: &[Vec<String>]) -> Vec<RadrootsJobInput> { let mut out = Vec::new(); for t in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("i")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("i")) { if t.len() < 2 { continue; @@ -163,7 +163,7 @@ pub fn parse_params(tags: &[Vec<String>]) -> Vec<RadrootsJobParam> { let mut params = Vec::new(); for t in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("param")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("param")) { if t.len() >= 3 { params.push(RadrootsJobParam { @@ -180,7 +180,7 @@ pub fn parse_amount_tag_sat( ) -> Result<Option<(u32, Option<String>)>, JobParseError> { let amt = match tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("amount")) + .find(|t| t.first().map(|s| s.as_str()) == Some("amount")) { Some(a) => a, None => return Ok(None), @@ -189,7 +189,7 @@ pub fn parse_amount_tag_sat( let msat_u64: u64 = msat_s .parse() .map_err(|e| JobParseError::InvalidNumber("amount", e))?; - if msat_u64 % 1000 != 0 { + if !msat_u64.is_multiple_of(1000) { return Err(JobParseError::NonWholeSats("amount")); } let sat_u64 = msat_u64 / 1000; @@ -212,7 +212,7 @@ pub fn push_amount_tag_msat(tags: &mut Vec<Vec<String>>, sat: u32, bolt11: Optio pub fn parse_bid_tag_sat(tags: &[Vec<String>]) -> Result<Option<u32>, JobParseError> { let bid = match tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("bid")) + .find(|t| t.first().map(|s| s.as_str()) == Some("bid")) { Some(b) => b, None => return Ok(None), diff --git a/crates/events_codec/src/list/decode.rs b/crates/events_codec/src/list/decode.rs @@ -75,10 +75,11 @@ fn validate_relay_tags(tags: &[Vec<String>]) -> Result<(), EventParseError> { if tag.len() > 3 { return Err(EventParseError::InvalidTag(TAG_R)); } - if let Some(marker) = tag.get(2) { - if marker != "read" && marker != "write" { - return Err(EventParseError::InvalidTag(TAG_R)); - } + if let Some(marker) = tag.get(2) + && marker != "read" + && marker != "write" + { + return Err(EventParseError::InvalidTag(TAG_R)); } } Ok(()) diff --git a/crates/events_codec/src/list/encode.rs b/crates/events_codec/src/list/encode.rs @@ -16,7 +16,7 @@ fn entry_tag(entry: &RadrootsListEntry) -> Result<Vec<String>, EventEncodeError> } let first = entry .values - .get(0) + .first() .ok_or(EventEncodeError::EmptyRequiredField("entry.values"))?; if first.trim().is_empty() { return Err(EventEncodeError::EmptyRequiredField("entry.values")); @@ -80,10 +80,11 @@ fn validate_relay_entries(entries: &[RadrootsListEntry]) -> Result<(), EventEnco if entry.values.len() > 2 { return Err(EventEncodeError::InvalidField("relay.marker")); } - if let Some(marker) = entry.values.get(1) { - if marker != "read" && marker != "write" { - return Err(EventEncodeError::InvalidField("relay.marker")); - } + if let Some(marker) = entry.values.get(1) + && marker != "read" + && marker != "write" + { + return Err(EventEncodeError::InvalidField("relay.marker")); } } Ok(()) diff --git a/crates/events_codec/src/list_set/encode.rs b/crates/events_codec/src/list_set/encode.rs @@ -17,10 +17,7 @@ const TAG_DESCRIPTION: &str = "description"; const TAG_IMAGE: &str = "image"; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } pub fn list_set_build_tags(list: &RadrootsListSet) -> Result<Vec<Vec<String>>, EventEncodeError> { @@ -47,7 +44,7 @@ pub fn list_set_build_tags(list: &RadrootsListSet) -> Result<Vec<Vec<String>>, E } let first = entry .values - .get(0) + .first() .ok_or(EventEncodeError::EmptyRequiredField("entry.values"))?; if first.trim().is_empty() { return Err(EventEncodeError::EmptyRequiredField("entry.values")); diff --git a/crates/events_codec/src/listing/decode.rs b/crates/events_codec/src/listing/decode.rs @@ -607,18 +607,18 @@ fn clean_value(value: &str) -> Option<String> { } fn set_if_empty(target: &mut String, value: Option<&String>) { - if target.trim().is_empty() { - if let Some(value) = value.and_then(|value| clean_value(value)) { - *target = value; - } + if target.trim().is_empty() + && let Some(value) = value.and_then(|value| clean_value(value)) + { + *target = value; } } fn set_optional(target: &mut Option<String>, value: Option<&String>) { - if target.is_none() { - if let Some(value) = value.and_then(|value| clean_value(value)) { - *target = Some(value); - } + if target.is_none() + && let Some(value) = value.and_then(|value| clean_value(value)) + { + *target = Some(value); } } diff --git a/crates/events_codec/src/listing/tags.rs b/crates/events_codec/src/listing/tags.rs @@ -189,71 +189,71 @@ pub fn listing_tags_with_options( return Err(EventEncodeError::Json); } - if options.include_inventory { - if let Some(inventory) = &listing.inventory_available { - tags.push(vec![TAG_INVENTORY.to_string(), inventory.to_string()]); - } + if options.include_inventory + && let Some(inventory) = &listing.inventory_available + { + tags.push(vec![TAG_INVENTORY.to_string(), inventory.to_string()]); } - if options.include_availability { - if let Some(availability) = &listing.availability { - match availability { - RadrootsListingAvailability::Status { status } => { + if options.include_availability + && let Some(availability) = &listing.availability + { + match availability { + RadrootsListingAvailability::Status { status } => { + tags.push(vec![ + TAG_STATUS.to_string(), + status_as_str(status).to_string(), + ]); + } + RadrootsListingAvailability::Window { start, end } => { + if let Some(start) = start { tags.push(vec![ - TAG_STATUS.to_string(), - status_as_str(status).to_string(), + TAG_RADROOTS_AVAILABILITY_START.to_string(), + start.to_string(), ]); } - RadrootsListingAvailability::Window { start, end } => { - if let Some(start) = start { - tags.push(vec![ - TAG_RADROOTS_AVAILABILITY_START.to_string(), - start.to_string(), - ]); - } - if let Some(end) = end { - tags.push(vec![TAG_EXPIRES_AT.to_string(), end.to_string()]); - } + if let Some(end) = end { + tags.push(vec![TAG_EXPIRES_AT.to_string(), end.to_string()]); } } } } - if options.include_delivery { - if let Some(method) = &listing.delivery_method { - let mut tag = Vec::with_capacity(3); - tag.push(TAG_DELIVERY.to_string()); - match method { - RadrootsListingDeliveryMethod::Pickup => tag.push("pickup".into()), - RadrootsListingDeliveryMethod::LocalDelivery => tag.push("local_delivery".into()), - RadrootsListingDeliveryMethod::Shipping => tag.push("shipping".into()), - RadrootsListingDeliveryMethod::Other { method } => { - tag.push("other".into()); - tag.push(method.clone()); - } + if options.include_delivery + && let Some(method) = &listing.delivery_method + { + let mut tag = Vec::with_capacity(3); + tag.push(TAG_DELIVERY.to_string()); + match method { + RadrootsListingDeliveryMethod::Pickup => tag.push("pickup".into()), + RadrootsListingDeliveryMethod::LocalDelivery => tag.push("local_delivery".into()), + RadrootsListingDeliveryMethod::Shipping => tag.push("shipping".into()), + RadrootsListingDeliveryMethod::Other { method } => { + tag.push("other".into()); + tag.push(method.clone()); } - tags.push(tag); } + tags.push(tag); } - if let Some(location) = &listing.location { - if let Some(primary) = clean_value(&location.primary) { - let mut tag = Vec::with_capacity(5); - tag.push(TAG_LOCATION.to_string()); - tag.push(primary); - if let Some(city) = location.city.as_deref().and_then(clean_value) { - tag.push(city); - } - if let Some(region) = location.region.as_deref().and_then(clean_value) { - tag.push(region); - } - if let Some(country) = location.country.as_deref().and_then(clean_value) { - tag.push(country); - } - tags.push(tag); - if options.include_geohash || options.include_gps { - push_location_geotags(&mut tags, location, options); - } + if let Some(location) = &listing.location + && let Some(primary) = clean_value(&location.primary) + { + let mut tag = Vec::with_capacity(5); + tag.push(TAG_LOCATION.to_string()); + tag.push(primary); + if let Some(city) = location.city.as_deref().and_then(clean_value) { + tag.push(city); + } + if let Some(region) = location.region.as_deref().and_then(clean_value) { + tag.push(region); + } + if let Some(country) = location.country.as_deref().and_then(clean_value) { + tag.push(country); + } + tags.push(tag); + if options.include_geohash || options.include_gps { + push_location_geotags(&mut tags, location, options); } } @@ -447,13 +447,12 @@ fn push_location_geotags( } if options.include_gps { - if lat.is_none() || lon.is_none() { - if let Some(geohash) = geohash.as_deref().or(location_geohash.as_deref()) { - if let Some((decoded_lat, decoded_lon)) = geohash_decode(geohash) { - lat = Some(decoded_lat); - lon = Some(decoded_lon); - } - } + if (lat.is_none() || lon.is_none()) + && let Some(geohash) = geohash.as_deref().or(location_geohash.as_deref()) + && let Some((decoded_lat, decoded_lon)) = geohash_decode(geohash) + { + lat = Some(decoded_lat); + lon = Some(decoded_lon); } if let (Some(lat), Some(lon)) = (lat, lon) { tags.push(vec![ @@ -509,7 +508,7 @@ fn geohash_encode(latitude: f64, longitude: f64, precision: usize) -> String { let mut min_lon = -180.0; while out.len() < precision { - if bits_total % 2 == 0 { + if bits_total.is_multiple_of(2) { let mid = (max_lon + min_lon) / 2.0; if longitude > mid { hash_value = (hash_value << 1) + 1; @@ -599,7 +598,7 @@ fn clean_value(value: &str) -> Option<String> { fn discount_tag_payload(discount: &RadrootsCoreDiscount) -> Result<String, EventEncodeError> { #[cfg(feature = "serde_json")] { - return serde_json::to_string(discount).map_err(|_| EventEncodeError::Json); + serde_json::to_string(discount).map_err(|_| EventEncodeError::Json) } #[cfg(not(feature = "serde_json"))] { diff --git a/crates/events_codec/src/message/tags.rs b/crates/events_codec/src/message/tags.rs @@ -16,10 +16,10 @@ fn validate_recipient(recipient: &RadrootsMessageRecipient) -> Result<(), EventE "recipients.public_key", )); } - if let Some(relay_url) = &recipient.relay_url { - if relay_url.trim().is_empty() { - return Err(EventEncodeError::EmptyRequiredField("recipients.relay_url")); - } + if let Some(relay_url) = &recipient.relay_url + && relay_url.trim().is_empty() + { + return Err(EventEncodeError::EmptyRequiredField("recipients.relay_url")); } Ok(()) } @@ -102,7 +102,7 @@ pub(crate) fn parse_recipients( let mut recipients = Vec::new(); for tag in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("p")) { recipients.push(parse_recipient_tag(tag)?); } @@ -117,7 +117,7 @@ pub(crate) fn parse_reply_tag( ) -> Result<Option<RadrootsNostrEventPtr>, EventParseError> { let tag = match tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("e")) + .find(|t| t.first().map(|s| s.as_str()) == Some("e")) { Some(tag) => tag, None => return Ok(None), @@ -140,7 +140,7 @@ pub(crate) fn parse_reply_tag( pub(crate) fn parse_subject_tag(tags: &[Vec<String>]) -> Result<Option<String>, EventParseError> { let tag = match tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("subject")) + .find(|t| t.first().map(|s| s.as_str()) == Some("subject")) { Some(tag) => tag, None => return Ok(None), diff --git a/crates/events_codec/src/message_file/decode.rs b/crates/events_codec/src/message_file/decode.rs @@ -19,7 +19,7 @@ const DEFAULT_KIND: u32 = KIND_MESSAGE_FILE; fn required_tag_value(tags: &[Vec<String>], key: &'static str) -> Result<String, EventParseError> { let value = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(key)) + .find(|t| t.first().map(|s| s.as_str()) == Some(key)) .and_then(|t| t.get(1)); let value = value.ok_or(EventParseError::MissingTag(key))?; if value.trim().is_empty() { @@ -34,7 +34,7 @@ fn optional_tag_value( ) -> Result<Option<String>, EventParseError> { let value = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(key)) + .find(|t| t.first().map(|s| s.as_str()) == Some(key)) .and_then(|t| t.get(1)); match value { Some(value) if value.trim().is_empty() => Err(EventParseError::InvalidTag(key)), @@ -59,7 +59,7 @@ fn parse_dimensions(value: &str) -> Result<RadrootsMessageFileDimensions, EventP fn parse_size(tags: &[Vec<String>]) -> Result<Option<u64>, EventParseError> { let value = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("size")) + .find(|t| t.first().map(|s| s.as_str()) == Some("size")) .and_then(|t| t.get(1)); let Some(value) = value else { return Ok(None); @@ -78,7 +78,7 @@ fn parse_dimensions_tag( ) -> Result<Option<RadrootsMessageFileDimensions>, EventParseError> { let value = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some("dim")) + .find(|t| t.first().map(|s| s.as_str()) == Some("dim")) .and_then(|t| t.get(1)); let Some(value) = value else { return Ok(None); @@ -93,7 +93,7 @@ fn parse_fallbacks(tags: &[Vec<String>]) -> Result<Vec<String>, EventParseError> let mut fallbacks = Vec::new(); for tag in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some("fallback")) + .filter(|t| t.first().map(|s| s.as_str()) == Some("fallback")) { let value = tag.get(1).ok_or(EventParseError::InvalidTag("fallback"))?; if value.trim().is_empty() { diff --git a/crates/events_codec/src/order/encode.rs b/crates/events_codec/src/order/encode.rs @@ -95,41 +95,51 @@ fn map_order_payload_error(error: RadrootsOrderPayloadError) -> EventEncodeError } #[cfg(feature = "serde_json")] -fn order_envelope_event_build<T: serde::Serialize>( - recipient_pubkey: &str, +struct OrderEnvelopeEventBuildParts<'a, T> { + recipient_pubkey: &'a str, message_type: RadrootsOrderEventType, - listing_addr: &str, - order_id: &str, - listing_event: Option<&RadrootsNostrEventPtr>, - root_event_id: Option<&RadrootsEventId>, - prev_event_id: Option<&RadrootsEventId>, - payload: &T, + listing_addr: &'a str, + order_id: &'a str, + listing_event: Option<&'a RadrootsNostrEventPtr>, + root_event_id: Option<&'a RadrootsEventId>, + prev_event_id: Option<&'a RadrootsEventId>, + payload: &'a T, +} + +#[cfg(feature = "serde_json")] +fn order_envelope_event_build<T: serde::Serialize>( + parts: OrderEnvelopeEventBuildParts<'_, T>, ) -> Result<WireEventParts, EventEncodeError> { - if message_type.requires_listing_snapshot() && listing_event.is_none() { + if parts.message_type.requires_listing_snapshot() && parts.listing_event.is_none() { return Err(EventEncodeError::EmptyRequiredField("listing_event.id")); } - if message_type.requires_order_chain() { - if root_event_id.is_none() { + if parts.message_type.requires_order_chain() { + if parts.root_event_id.is_none() { return Err(EventEncodeError::EmptyRequiredField("root_event_id")); } - if prev_event_id.is_none() { + if parts.prev_event_id.is_none() { return Err(EventEncodeError::EmptyRequiredField("prev_event_id")); } } - let envelope = RadrootsOrderEnvelope::new(message_type, listing_addr, order_id, payload); + let envelope = RadrootsOrderEnvelope::new( + parts.message_type, + parts.listing_addr, + parts.order_id, + parts.payload, + ); envelope.validate().map_err(map_order_envelope_error)?; let content = serde_json::to_string(&envelope).map_err(|_| EventEncodeError::Json)?; let tags = order_envelope_tags( - recipient_pubkey, - listing_addr, - Some(order_id), - listing_event, - root_event_id.map(RadrootsEventId::as_str), - prev_event_id.map(RadrootsEventId::as_str), + parts.recipient_pubkey, + parts.listing_addr, + Some(parts.order_id), + parts.listing_event, + parts.root_event_id.map(RadrootsEventId::as_str), + parts.prev_event_id.map(RadrootsEventId::as_str), )?; Ok(WireEventParts { - kind: message_type.kind(), + kind: parts.message_type.kind(), content, tags, }) @@ -141,16 +151,16 @@ pub fn order_request_event_build( payload: &RadrootsOrderRequest, ) -> Result<WireEventParts, EventEncodeError> { payload.validate().map_err(map_order_payload_error)?; - order_envelope_event_build( - &payload.seller_pubkey, - RadrootsOrderEventType::OrderRequested, - &payload.listing_addr, - &payload.order_id, - Some(listing_event), - None, - None, + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.seller_pubkey, + message_type: RadrootsOrderEventType::OrderRequested, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: Some(listing_event), + root_event_id: None, + prev_event_id: None, payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -160,16 +170,16 @@ pub fn order_decision_event_build( payload: &RadrootsOrderDecision, ) -> Result<WireEventParts, EventEncodeError> { payload.validate().map_err(map_order_payload_error)?; - order_envelope_event_build( - &payload.buyer_pubkey, - RadrootsOrderEventType::OrderDecision, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.buyer_pubkey, + message_type: RadrootsOrderEventType::OrderDecision, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -185,16 +195,16 @@ pub fn order_revision_proposal_event_build( if payload.prev_event_id.as_str() != prev_event_id.as_str() { return Err(EventEncodeError::InvalidField("prev_event_id")); } - order_envelope_event_build( - &payload.buyer_pubkey, - RadrootsOrderEventType::OrderRevisionProposed, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.buyer_pubkey, + message_type: RadrootsOrderEventType::OrderRevisionProposed, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -210,16 +220,16 @@ pub fn order_revision_decision_event_build( if payload.prev_event_id.as_str() != prev_event_id.as_str() { return Err(EventEncodeError::InvalidField("prev_event_id")); } - order_envelope_event_build( - &payload.seller_pubkey, - RadrootsOrderEventType::OrderRevisionDecision, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.seller_pubkey, + message_type: RadrootsOrderEventType::OrderRevisionDecision, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -229,16 +239,16 @@ pub fn order_fulfillment_update_event_build( payload: &RadrootsOrderFulfillmentUpdate, ) -> Result<WireEventParts, EventEncodeError> { payload.validate().map_err(map_order_payload_error)?; - order_envelope_event_build( - &payload.buyer_pubkey, - RadrootsOrderEventType::FulfillmentUpdated, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.buyer_pubkey, + message_type: RadrootsOrderEventType::FulfillmentUpdated, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -248,16 +258,16 @@ pub fn order_cancellation_event_build( payload: &RadrootsOrderCancellation, ) -> Result<WireEventParts, EventEncodeError> { payload.validate().map_err(map_order_payload_error)?; - order_envelope_event_build( - &payload.seller_pubkey, - RadrootsOrderEventType::OrderCancelled, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.seller_pubkey, + message_type: RadrootsOrderEventType::OrderCancelled, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -267,16 +277,16 @@ pub fn order_receipt_event_build( payload: &RadrootsOrderReceipt, ) -> Result<WireEventParts, EventEncodeError> { payload.validate().map_err(map_order_payload_error)?; - order_envelope_event_build( - &payload.seller_pubkey, - RadrootsOrderEventType::BuyerReceipt, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.seller_pubkey, + message_type: RadrootsOrderEventType::BuyerReceipt, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -292,16 +302,16 @@ pub fn order_payment_record_event_build( if payload.previous_event_id.as_str() != prev_event_id.as_str() { return Err(EventEncodeError::InvalidField("previous_event_id")); } - order_envelope_event_build( - &payload.seller_pubkey, - RadrootsOrderEventType::PaymentRecorded, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.seller_pubkey, + message_type: RadrootsOrderEventType::PaymentRecorded, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } #[cfg(feature = "serde_json")] @@ -317,14 +327,14 @@ pub fn order_settlement_decision_event_build( if payload.previous_event_id.as_str() != prev_event_id.as_str() { return Err(EventEncodeError::InvalidField("previous_event_id")); } - order_envelope_event_build( - &payload.buyer_pubkey, - RadrootsOrderEventType::SettlementDecision, - &payload.listing_addr, - &payload.order_id, - None, - Some(root_event_id), - Some(prev_event_id), + order_envelope_event_build(OrderEnvelopeEventBuildParts { + recipient_pubkey: &payload.buyer_pubkey, + message_type: RadrootsOrderEventType::SettlementDecision, + listing_addr: &payload.listing_addr, + order_id: &payload.order_id, + listing_event: None, + root_event_id: Some(root_event_id), + prev_event_id: Some(prev_event_id), payload, - ) + }) } diff --git a/crates/events_codec/src/order/tags.rs b/crates/events_codec/src/order/tags.rs @@ -16,10 +16,7 @@ pub const TAG_LISTING_EVENT: &str = "listing_event"; #[inline] fn push_tag(tags: &mut Vec<Vec<String>>, name: &'static str, value: impl Into<String>) { - let mut tag = Vec::with_capacity(2); - tag.push(name.to_owned()); - tag.push(value.into()); - tags.push(tag); + tags.push(vec![name.to_owned(), value.into()]); } fn build_event_ptr_tag( diff --git a/crates/events_codec/src/plot/decode.rs b/crates/events_codec/src/plot/decode.rs @@ -25,7 +25,7 @@ const DEFAULT_KIND: u32 = KIND_PLOT; fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or(EventParseError::MissingTag(TAG_D))?; let value = tag .get(1) @@ -41,7 +41,7 @@ fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { fn parse_farm_ref(tags: &[Vec<String>]) -> Result<RadrootsFarmRef, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_A)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_A)) .ok_or(EventParseError::MissingTag(TAG_A))?; let value = tag .get(1) @@ -73,7 +73,7 @@ fn parse_farm_ref(tags: &[Vec<String>]) -> Result<RadrootsFarmRef, EventParseErr fn parse_farm_pubkey(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_P)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_P)) .ok_or(EventParseError::MissingTag(TAG_P))?; let value = tag .get(1) diff --git a/crates/events_codec/src/plot/encode.rs b/crates/events_codec/src/plot/encode.rs @@ -23,10 +23,7 @@ const TAG_A: &str = "a"; const TAG_P: &str = "p"; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } fn farm_address(farm: &RadrootsFarmRef) -> String { diff --git a/crates/events_codec/src/post/encode.rs b/crates/events_codec/src/post/encode.rs @@ -115,15 +115,15 @@ fn push_address_ref( if parsed.kind == KIND_FARM { return Err(EventEncodeError::InvalidField("address_refs")); } - if let Some(kind) = event_kind { - if *kind != parsed.kind { - return Err(EventEncodeError::InvalidField("address_refs")); - } + if let Some(kind) = event_kind + && *kind != parsed.kind + { + return Err(EventEncodeError::InvalidField("address_refs")); } - if let Some(author) = author.as_deref() { - if author != parsed.pubkey { - return Err(EventEncodeError::InvalidField("address_refs")); - } + if let Some(author) = author.as_deref() + && author != parsed.pubkey + { + return Err(EventEncodeError::InvalidField("address_refs")); } let mut tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len)); tag.push(TAG_A.to_string()); @@ -162,10 +162,10 @@ fn push_quote_ref( } => { let parsed = parse_address_tag(address, "quote_refs") .map_err(|_| EventEncodeError::InvalidField("quote_refs"))?; - if let Some(kind) = event_kind { - if *kind != parsed.kind { - return Err(EventEncodeError::InvalidField("quote_refs")); - } + if let Some(kind) = event_kind + && *kind != parsed.kind + { + return Err(EventEncodeError::InvalidField("quote_refs")); } let mut tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len)); tag.push(TAG_Q.to_string()); diff --git a/crates/events_codec/src/profile/decode.rs b/crates/events_codec/src/profile/decode.rs @@ -39,7 +39,7 @@ fn parse_bot(value: &Value) -> Option<String> { fn profile_type_from_tags(tags: &[Vec<String>]) -> Option<RadrootsProfileType> { tags.iter() - .filter(|tag| tag.get(0).map(|v| v.as_str()) == Some(RADROOTS_PROFILE_TYPE_TAG_KEY)) + .filter(|tag| tag.first().map(|v| v.as_str()) == Some(RADROOTS_PROFILE_TYPE_TAG_KEY)) .filter_map(|tag| tag.get(1)) .find_map(|value| radroots_profile_type_from_tag_value(value)) } diff --git a/crates/events_codec/src/profile/encode.rs b/crates/events_codec/src/profile/encode.rs @@ -16,10 +16,7 @@ use alloc::{string::String, vec::Vec}; use crate::wire::WireEventParts; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } pub fn profile_type_tags(profile_type: RadrootsProfileType) -> Vec<Vec<String>> { diff --git a/crates/events_codec/src/reaction/encode.rs b/crates/events_codec/src/reaction/encode.rs @@ -79,15 +79,15 @@ fn push_reaction_target( } => { let parsed = parse_address_tag(address, "target.address") .map_err(|_| EventEncodeError::InvalidField("target.address"))?; - if let Some(kind) = event_kind { - if *kind != parsed.kind { - return Err(EventEncodeError::InvalidField("target.kind")); - } + if let Some(kind) = event_kind + && *kind != parsed.kind + { + return Err(EventEncodeError::InvalidField("target.kind")); } - if let Some(author) = author.as_deref() { - if author != parsed.pubkey { - return Err(EventEncodeError::InvalidField("target.author")); - } + if let Some(author) = author.as_deref() + && author != parsed.pubkey + { + return Err(EventEncodeError::InvalidField("target.author")); } let mut address_tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len)); address_tag.push("a".to_string()); diff --git a/crates/events_codec/src/resource_area/decode.rs b/crates/events_codec/src/resource_area/decode.rs @@ -19,7 +19,7 @@ const DEFAULT_KIND: u32 = KIND_RESOURCE_AREA; fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or(EventParseError::MissingTag(TAG_D))?; let value = tag .get(1) diff --git a/crates/events_codec/src/resource_area/encode.rs b/crates/events_codec/src/resource_area/encode.rs @@ -22,10 +22,7 @@ const TAG_A: &str = "a"; const TAG_P: &str = "p"; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } fn resource_area_address(area: &RadrootsResourceAreaRef) -> Result<String, EventEncodeError> { diff --git a/crates/events_codec/src/resource_cap/decode.rs b/crates/events_codec/src/resource_cap/decode.rs @@ -20,7 +20,7 @@ const DEFAULT_KIND: u32 = KIND_RESOURCE_HARVEST_CAP; fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or(EventParseError::MissingTag(TAG_D))?; let value = tag .get(1) diff --git a/crates/events_codec/src/resource_cap/encode.rs b/crates/events_codec/src/resource_cap/encode.rs @@ -25,10 +25,7 @@ const TAG_START: &str = "start"; const TAG_END: &str = "end"; fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { - let mut tag = Vec::with_capacity(2); - tag.push(key.to_string()); - tag.push(value.to_string()); - tags.push(tag); + tags.push(vec![key.to_string(), value.to_string()]); } fn resource_area_address(cap: &RadrootsResourceHarvestCap) -> Result<String, EventEncodeError> { @@ -65,10 +62,10 @@ pub fn resource_harvest_cap_build_tags( push_tag(&mut tags, TAG_A, &addr); push_tag(&mut tags, TAG_P, &cap.resource_area.pubkey); push_tag(&mut tags, TAG_KEY, &cap.product.key); - if let Some(category) = cap.product.category.as_deref() { - if !category.trim().is_empty() { - push_tag(&mut tags, TAG_CATEGORY, category); - } + if let Some(category) = cap.product.category.as_deref() + && !category.trim().is_empty() + { + push_tag(&mut tags, TAG_CATEGORY, category); } push_tag(&mut tags, TAG_START, &cap.start.to_string()); push_tag(&mut tags, TAG_END, &cap.end.to_string()); diff --git a/crates/events_codec/src/social_helpers.rs b/crates/events_codec/src/social_helpers.rs @@ -36,16 +36,16 @@ pub(crate) fn validate_date_tag(value: &str, tag: &'static str) -> Result<(), Ev pub(crate) fn is_date(value: &str) -> bool { let bytes = value.as_bytes(); bytes.len() == 10 - && matches!(bytes[0], b'0'..=b'9') - && matches!(bytes[1], b'0'..=b'9') - && matches!(bytes[2], b'0'..=b'9') - && matches!(bytes[3], b'0'..=b'9') + && bytes[0].is_ascii_digit() + && bytes[1].is_ascii_digit() + && bytes[2].is_ascii_digit() + && bytes[3].is_ascii_digit() && bytes[4] == b'-' - && matches!(bytes[5], b'0'..=b'9') - && matches!(bytes[6], b'0'..=b'9') + && bytes[5].is_ascii_digit() + && bytes[6].is_ascii_digit() && bytes[7] == b'-' - && matches!(bytes[8], b'0'..=b'9') - && matches!(bytes[9], b'0'..=b'9') + && bytes[8].is_ascii_digit() + && bytes[9].is_ascii_digit() } pub(crate) fn validate_end_after_start( diff --git a/crates/geocoder/src/geocoder.rs b/crates/geocoder/src/geocoder.rs @@ -144,10 +144,7 @@ fn finalize_country_center( result: Result<Option<GeocoderPoint>, GeocoderError>, country_id: &str, ) -> Result<GeocoderPoint, GeocoderError> { - let maybe_point = match result { - Ok(maybe_point) => maybe_point, - Err(err) => return Err(err), - }; + let maybe_point = result?; if let Some(point) = maybe_point { return Ok(point); } diff --git a/crates/identity/src/identity.rs b/crates/identity/src/identity.rs @@ -380,14 +380,14 @@ impl RadrootsIdentity { }; #[cfg(feature = "profile")] { - return RadrootsIdentityFile { + RadrootsIdentityFile { secret_key, public_key: Some(self.public_key_hex()), identifier, metadata, application_handler, profile, - }; + } } #[cfg(not(feature = "profile"))] { diff --git a/crates/identity/src/username.rs b/crates/identity/src/username.rs @@ -12,7 +12,7 @@ pub fn radroots_username_is_valid(username: &str) -> bool { return false; } let len = username.len(); - if len < RADROOTS_USERNAME_MIN_LEN || len > RADROOTS_USERNAME_MAX_LEN { + if !(RADROOTS_USERNAME_MIN_LEN..=RADROOTS_USERNAME_MAX_LEN).contains(&len) { return false; } let bytes = username.as_bytes(); diff --git a/crates/log/src/lib.rs b/crates/log/src/lib.rs @@ -41,23 +41,19 @@ pub fn init_no_std() -> Result<()> { pub fn init_default() -> Result<()> { #[cfg(feature = "std")] { - return init_stdout(); + init_stdout() } #[cfg(not(feature = "std"))] { - return init_no_std(); + init_no_std() } } -pub fn coverage_branch_probe(input: bool) -> &'static str { - if input { "log" } else { "log" } -} - #[cfg(test)] mod tests { - use super::{coverage_branch_probe, log_debug, log_error, log_info}; #[cfg(not(feature = "std"))] use super::{init_default, init_no_std}; + use super::{log_debug, log_error, log_info}; #[test] fn logging_helpers_are_callable() { @@ -106,12 +102,6 @@ mod tests { log_debug("debug"); } - #[test] - fn coverage_branch_probe_hits_both_paths() { - assert_eq!(coverage_branch_probe(true), "log"); - assert_eq!(coverage_branch_probe(false), "log"); - } - #[cfg(not(feature = "std"))] #[test] fn no_std_init_paths_are_callable() { diff --git a/crates/net/build.rs b/crates/net/build.rs @@ -38,12 +38,11 @@ fn main() { println!("cargo:rustc-env=BUILD_TIME_UNIX={}", build_time_unix); let rustc_bin = env::var("RUSTC").expect("missing required env var RUSTC"); - if let Ok(out) = Command::new(rustc_bin).arg("--version").output() { - if out.status.success() { - if let Ok(ver) = String::from_utf8(out.stdout) { - println!("cargo:rustc-env=RUSTC_VERSION={}", ver.trim()); - } - } + if let Ok(out) = Command::new(rustc_bin).arg("--version").output() + && out.status.success() + && let Ok(ver) = String::from_utf8(out.stdout) + { + println!("cargo:rustc-env=RUSTC_VERSION={}", ver.trim()); } let git_hash = Command::new("git") diff --git a/crates/net/src/builder.rs b/crates/net/src/builder.rs @@ -2,21 +2,12 @@ use crate::config::NetConfig; use crate::error::Result; use crate::{Net, NetHandle}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct NetBuilder { config: NetConfig, manage_runtime: bool, } -impl Default for NetBuilder { - fn default() -> Self { - Self { - config: NetConfig::default(), - manage_runtime: false, - } - } -} - impl NetBuilder { pub fn new() -> Self { Self::default() @@ -45,7 +36,7 @@ impl NetBuilder { } pub fn coverage_branch_probe(input: bool) -> bool { - if input { true } else { false } + input } #[cfg(test)] diff --git a/crates/net/src/keys.rs b/crates/net/src/keys.rs @@ -57,8 +57,7 @@ impl KeysManager { } pub fn is_valid_hex32(s: &str) -> bool { - let is_hex = s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit()); - is_hex + s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit()) } pub fn is_valid_nsec(s: &str) -> bool { s.starts_with("nsec1") @@ -111,26 +110,23 @@ impl KeysManager { pub fn load_from_path_auto(&mut self, path: impl AsRef<Path>) -> Result<()> { let p = path.as_ref(); - match std::fs::read_to_string(p) { - Ok(s) => { - if let Ok(jf) = serde_json::from_str::<KeysFile>(&s) { - return self.load_from_hex32(&jf.key).map(|_| { - self.state.source = Some(p.to_path_buf()); - }); - } - let trimmed = s.trim(); - if Self::is_valid_nsec(trimmed) { - return self.load_from_nsec(trimmed).map(|_| { - self.state.source = Some(p.to_path_buf()); - }); - } - if Self::is_valid_hex32(trimmed) { - return self.load_from_hex32(trimmed).map(|_| { - self.state.source = Some(p.to_path_buf()); - }); - } + if let Ok(s) = std::fs::read_to_string(p) { + if let Ok(jf) = serde_json::from_str::<KeysFile>(&s) { + return self.load_from_hex32(&jf.key).map(|_| { + self.state.source = Some(p.to_path_buf()); + }); + } + let trimmed = s.trim(); + if Self::is_valid_nsec(trimmed) { + return self.load_from_nsec(trimmed).map(|_| { + self.state.source = Some(p.to_path_buf()); + }); + } + if Self::is_valid_hex32(trimmed) { + return self.load_from_hex32(trimmed).map(|_| { + self.state.source = Some(p.to_path_buf()); + }); } - Err(_) => {} } match std::fs::read(p) { Ok(bytes) if bytes.len() == 32 => { diff --git a/crates/net/src/net.rs b/crates/net/src/net.rs @@ -222,13 +222,13 @@ mod tests { assert!(net.selected_nostr_keys().is_some()); assert!(net.selected_nostr_signer().is_some()); - let remote = RadrootsNostrSignerCapability::RemoteSession( + let remote = RadrootsNostrSignerCapability::RemoteSession(Box::new( RadrootsNostrRemoteSessionSignerCapability::new( RadrootsNostrSignerConnectionId::new_v7(), RadrootsIdentity::generate().to_public(), RadrootsIdentity::generate().to_public(), ), - ); + )); net.set_nostr_signer(Some(remote.clone())); assert_eq!(net.selected_nostr_signer(), Some(remote)); assert!(net.selected_nostr_keys().is_none()); diff --git a/crates/net/src/nostr_client/manager.rs b/crates/net/src/nostr_client/manager.rs @@ -74,10 +74,10 @@ impl NostrClientManager { } pub fn stop_post_event_stream(&self) { - if let Ok(mut g) = self.inner.post_events_stream.lock() { - if let Some(h) = g.take() { - h.abort(); - } + if let Ok(mut g) = self.inner.post_events_stream.lock() + && let Some(h) = g.take() + { + h.abort(); } } diff --git a/crates/nostr/src/client.rs b/crates/nostr/src/client.rs @@ -218,7 +218,7 @@ pub async fn radroots_nostr_send_event( client: &RadrootsNostrClient, event: RadrootsNostrEventBuilder, ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { - Ok(client.send_event_builder(event).await?) + client.send_event_builder(event).await } pub async fn radroots_nostr_fetch_event_by_id( diff --git a/crates/nostr/src/error.rs b/crates/nostr/src/error.rs @@ -57,7 +57,7 @@ pub enum RadrootsNostrError { #[derive(Debug, Error)] pub enum RadrootsNostrTagsResolveError { #[error("Missing public key 'p' tag in encrypted event: {0:?}")] - MissingPTag(nostr::Event), + MissingPTag(Box<nostr::Event>), #[error("Encrypted event recipient mismatch")] NotRecipient, diff --git a/crates/nostr/src/event_adapters.rs b/crates/nostr/src/event_adapters.rs @@ -43,7 +43,7 @@ pub fn to_profile_event_metadata( .iter() .filter_map(|tag| { let values = tag.as_slice(); - if values.get(0).map(|v| v.as_str()) != Some(RADROOTS_PROFILE_TYPE_TAG_KEY) { + if values.first().map(|v| v.as_str()) != Some(RADROOTS_PROFILE_TYPE_TAG_KEY) { return None; } values diff --git a/crates/nostr/src/events/application_handler.rs b/crates/nostr/src/events/application_handler.rs @@ -55,10 +55,10 @@ pub fn radroots_nostr_build_application_handler_event( .unwrap_or_else(|| spec.kinds[0].to_string()); let mut content = String::new(); - if let Some(md) = spec.metadata.as_ref() { - if radroots_nostr_metadata_has_fields(md) { - content = serde_json::to_string(md).unwrap_or_default(); - } + if let Some(md) = spec.metadata.as_ref() + && radroots_nostr_metadata_has_fields(md) + { + content = serde_json::to_string(md).unwrap_or_default(); } let mut tags = Vec::new(); @@ -109,10 +109,10 @@ pub async fn radroots_nostr_publish_application_handler( ) -> Result<crate::types::RadrootsNostrOutput<crate::types::RadrootsNostrEventId>, RadrootsNostrError> { let mut spec = spec.clone(); - if spec.identifier.is_none() { - if let Some(existing) = fetch_existing_identifier(client, &spec).await? { - spec.identifier = Some(existing); - } + if spec.identifier.is_none() + && let Some(existing) = fetch_existing_identifier(client, &spec).await? + { + spec.identifier = Some(existing); } let builder = radroots_nostr_build_application_handler_event(&spec)?; crate::client::radroots_nostr_send_event(client, builder).await diff --git a/crates/nostr/src/events/metadata.rs b/crates/nostr/src/events/metadata.rs @@ -24,7 +24,7 @@ pub async fn radroots_nostr_post_metadata_event( md: &RadrootsNostrMetadata, ) -> Result<RadrootsNostrOutput<RadrootsNostrEventId>, RadrootsNostrError> { let builder = radroots_nostr_build_metadata_event(md); - Ok(client.send_event_builder(builder).await?) + client.send_event_builder(builder).await } #[cfg(feature = "client")] @@ -40,7 +40,7 @@ pub async fn radroots_nostr_fetch_metadata_for_author( let fetched = client.fetch_events(filter, timeout).await?; let mut latest: Option<RadrootsNostrEvent> = None; - for ev in stored.into_iter().chain(fetched.into_iter()) { + for ev in stored.into_iter().chain(fetched) { if ev.kind != RadrootsNostrKind::Metadata { continue; } diff --git a/crates/nostr/src/events/post.rs b/crates/nostr/src/events/post.rs @@ -37,12 +37,11 @@ pub fn radroots_nostr_build_post_reply_event( let parent_pubkey = RadrootsNostrPublicKey::from_hex(parent_author_hex)?; let mut tags: Vec<RadrootsNostrTag> = Vec::new(); - if let Some(root_hex) = root_event_id_hex { - if !root_hex.is_empty() { - if let Ok(root_id) = RadrootsNostrEventId::from_hex(root_hex) { - tags.push(RadrootsNostrTag::event(root_id)); - } - } + if let Some(root_hex) = root_event_id_hex + && !root_hex.is_empty() + && let Ok(root_id) = RadrootsNostrEventId::from_hex(root_hex) + { + tags.push(RadrootsNostrTag::event(root_id)); } tags.push(RadrootsNostrTag::event(parent_id)); diff --git a/crates/nostr/src/identity_profile.rs b/crates/nostr/src/identity_profile.rs @@ -63,12 +63,9 @@ pub async fn radroots_nostr_bootstrap_service_presence( client.wait_for_connection(connect_timeout).await; let profile_published = - match radroots_nostr_publish_identity_profile_with_type(client, identity, profile_type) + radroots_nostr_publish_identity_profile_with_type(client, identity, profile_type) .await? - { - Some(_) => true, - None => false, - }; + .is_some(); if radroots_nostr_metadata_has_fields(metadata) && !profile_published { let builder = diff --git a/crates/nostr/src/nip17.rs b/crates/nostr/src/nip17.rs @@ -44,7 +44,7 @@ pub enum RadrootsNip17Error { #[derive(Clone, Debug)] pub enum RadrootsNip17Rumor { Message(RadrootsParsedData<RadrootsMessage>), - MessageFile(RadrootsParsedData<RadrootsMessageFile>), + MessageFile(Box<RadrootsParsedData<RadrootsMessageFile>>), } #[derive(Clone, Debug)] @@ -205,7 +205,7 @@ where content, tags, )?; - Ok(RadrootsNip17Rumor::MessageFile(metadata)) + Ok(RadrootsNip17Rumor::MessageFile(Box::new(metadata))) } other => Err(RadrootsNip17Error::UnsupportedRumorKind(other)), } diff --git a/crates/nostr/src/tags.rs b/crates/nostr/src/tags.rs @@ -34,7 +34,7 @@ pub fn radroots_nostr_tag_relays_parse( } } -pub fn radroots_nostr_tags_match<'a>(tag: &'a RadrootsNostrTag) -> Option<(&'a str, &'a [String])> { +pub fn radroots_nostr_tags_match(tag: &RadrootsNostrTag) -> Option<(&str, &[String])> { let values = tag.as_slice(); values .split_first() @@ -43,10 +43,11 @@ pub fn radroots_nostr_tags_match<'a>(tag: &'a RadrootsNostrTag) -> Option<(&'a s pub fn radroots_nostr_tag_match_l(tag: &RadrootsNostrTag) -> Option<(&str, f64)> { let values = tag.as_slice(); - if values.len() >= 3 && values[0].eq_ignore_ascii_case("l") { - if let Ok(value) = values[1].parse::<f64>() { - return Some((values[2].as_str(), value)); - } + if values.len() >= 3 + && values[0].eq_ignore_ascii_case("l") + && let Ok(value) = values[1].parse::<f64>() + { + return Some((values[2].as_str(), value)); } None } @@ -102,7 +103,7 @@ pub fn radroots_nostr_tags_resolve( None } }) - .ok_or_else(|| RadrootsNostrTagsResolveError::MissingPTag(event.clone()))?; + .ok_or_else(|| RadrootsNostrTagsResolveError::MissingPTag(Box::new(event.clone())))?; if recipient != keys.public_key() { return Err(RadrootsNostrTagsResolveError::NotRecipient); } diff --git a/crates/nostr_accounts/src/manager.rs b/crates/nostr_accounts/src/manager.rs @@ -506,13 +506,13 @@ impl RadrootsNostrAccountsManager { record: RadrootsNostrAccountRecord, ) -> Result<RadrootsNostrSignerCapability, RadrootsNostrAccountsError> { let availability = self.local_signer_availability(&record)?; - Ok(RadrootsNostrSignerCapability::LocalAccount( + Ok(RadrootsNostrSignerCapability::LocalAccount(Box::new( RadrootsNostrLocalSignerCapability::new( record.account_id, record.public_identity, availability, ), - )) + ))) } fn local_signer_availability( @@ -1839,13 +1839,13 @@ mod tests { .is_some() ); - let remote_signer = RadrootsNostrSignerCapability::RemoteSession( + let remote_signer = RadrootsNostrSignerCapability::RemoteSession(Box::new( radroots_nostr_signer::prelude::RadrootsNostrRemoteSessionSignerCapability::new( radroots_nostr_signer::prelude::RadrootsNostrSignerConnectionId::new_v7(), RadrootsIdentity::generate().to_public(), RadrootsIdentity::generate().to_public(), ), - ); + )); assert!( manager .resolve_signing_identity_for_signer(&remote_signer) diff --git a/crates/nostr_connect/src/message.rs b/crates/nostr_connect/src/message.rs @@ -416,10 +416,10 @@ impl RadrootsNostrConnectResponse { method: &RadrootsNostrConnectMethod, envelope: RadrootsNostrConnectResponseEnvelope, ) -> Result<Self, RadrootsNostrConnectError> { - if let (Some(Value::String(result)), Some(url)) = (&envelope.result, &envelope.error) { - if result == "auth_url" { - return Ok(Self::AuthUrl(validate_url(url)?)); - } + if let (Some(Value::String(result)), Some(url)) = (&envelope.result, &envelope.error) + && result == "auth_url" + { + return Ok(Self::AuthUrl(validate_url(url)?)); } if let Some(error) = envelope.error { diff --git a/crates/nostr_ndb/src/ingest.rs b/crates/nostr_ndb/src/ingest.rs @@ -1,7 +1,10 @@ -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Default, Eq, PartialEq)] pub enum RadrootsNostrNdbIngestSource { + #[default] Client, - Relay { relay_url: Option<String> }, + Relay { + relay_url: Option<String>, + }, } impl RadrootsNostrNdbIngestSource { @@ -33,9 +36,3 @@ impl RadrootsNostrNdbIngestSource { } } } - -impl Default for RadrootsNostrNdbIngestSource { - fn default() -> Self { - Self::Client - } -} diff --git a/crates/nostr_runtime/src/runtime.rs b/crates/nostr_runtime/src/runtime.rs @@ -178,10 +178,10 @@ impl RadrootsNostrRuntime { } } - if let Ok(mut guard) = self.inner.monitor_task.lock() { - if let Some(handle) = guard.take() { - handle.abort(); - } + if let Ok(mut guard) = self.inner.monitor_task.lock() + && let Some(handle) = guard.take() + { + handle.abort(); } let _ = self @@ -347,10 +347,10 @@ impl RadrootsNostrRuntime { } }); - if let Ok(mut guard) = self.inner.monitor_task.lock() { - if let Some(existing) = guard.replace(handle) { - existing.abort(); - } + if let Ok(mut guard) = self.inner.monitor_task.lock() + && let Some(existing) = guard.replace(handle) + { + existing.abort(); } } @@ -413,16 +413,16 @@ fn spawn_subscription_worker( let kind = event.kind.as_u16(); since_unix = Some(event.created_at.as_secs().saturating_add(1)); - if let Some(sink) = inner.event_sink.as_ref() { - if let Err(message) = sink.ingest_event(&event) { - if let Ok(mut guard) = inner.last_error.lock() { - *guard = Some(message.clone()); - } - let _ = inner - .queue_tx - .send(RadrootsNostrRuntimeEvent::Error { message }) - .await; + if let Some(sink) = inner.event_sink.as_ref() + && let Err(message) = sink.ingest_event(&event) + { + if let Ok(mut guard) = inner.last_error.lock() { + *guard = Some(message.clone()); } + let _ = inner + .queue_tx + .send(RadrootsNostrRuntimeEvent::Error { message }) + .await; } let _ = inner diff --git a/crates/nostr_signer/src/backend.rs b/crates/nostr_signer/src/backend.rs @@ -44,7 +44,7 @@ pub enum RadrootsNostrSignerPublishTransition { MarkedPublished(RadrootsNostrSignerPublishWorkflowRecord), Finalized { workflow_id: RadrootsNostrSignerWorkflowId, - connection: RadrootsNostrSignerConnectionRecord, + connection: Box<RadrootsNostrSignerConnectionRecord>, }, Cancelled(RadrootsNostrSignerPublishWorkflowRecord), } @@ -251,12 +251,15 @@ impl RadrootsNostrSignerBackendCapabilities { pub fn all_signers(&self) -> Vec<RadrootsNostrSignerCapability> { let mut signers = Vec::new(); if let Some(local_signer) = self.local_signer.clone() { - signers.push(RadrootsNostrSignerCapability::LocalAccount(local_signer)); + signers.push(RadrootsNostrSignerCapability::LocalAccount(Box::new( + local_signer, + ))); } signers.extend( self.remote_sessions .iter() .cloned() + .map(Box::new) .map(RadrootsNostrSignerCapability::RemoteSession), ); signers @@ -284,7 +287,7 @@ impl RadrootsNostrSignerPublishTransition { ) -> Self { Self::Finalized { workflow_id, - connection, + connection: Box::new(connection), } } @@ -303,7 +306,7 @@ impl RadrootsNostrSignerPublishTransition { pub fn finalized_connection(&self) -> Option<&RadrootsNostrSignerConnectionRecord> { match self { - Self::Finalized { connection, .. } => Some(connection), + Self::Finalized { connection, .. } => Some(connection.as_ref()), _ => None, } } @@ -623,7 +626,7 @@ impl RadrootsNostrSignerBackend for RadrootsNostrEmbeddedSignerBackend { ) -> Result<RadrootsNostrSignerSignOutput, RadrootsNostrSignerError> { let event = unsigned_event.sign_with_keys(self.signer_identity.keys())?; Ok(RadrootsNostrSignerSignOutput::new( - RadrootsNostrSignerCapability::LocalAccount(self.local_signer_capability()), + RadrootsNostrSignerCapability::LocalAccount(Box::new(self.local_signer_capability())), event, )) } @@ -700,7 +703,7 @@ mod tests { lookup: RadrootsNostrSignerSessionLookup, ) -> RadrootsNostrSignerConnectionRecord { match lookup { - RadrootsNostrSignerSessionLookup::Connection(found) => found, + RadrootsNostrSignerSessionLookup::Connection(found) => *found, other => panic!("unexpected session lookup: {other:?}"), } } @@ -724,7 +727,7 @@ mod tests { RadrootsNostrSignerPublishTransition::Finalized { workflow_id, connection, - } => (workflow_id, connection), + } => (workflow_id, *connection), other => panic!("unexpected finalize transition: {other:?}"), } } @@ -1738,7 +1741,9 @@ mod tests { assert_eq!( capabilities.all_signers(), - vec![crate::capability::RadrootsNostrSignerCapability::RemoteSession(remote)] + vec![ + crate::capability::RadrootsNostrSignerCapability::RemoteSession(Box::new(remote,)) + ] ); let valid_identity = synthetic_public_identity(0xb2); @@ -1786,7 +1791,9 @@ mod tests { assert!( std::panic::catch_unwind(|| { expect_registration_required( - RadrootsNostrSignerConnectEvaluation::ExistingConnection(connection.clone()), + RadrootsNostrSignerConnectEvaluation::ExistingConnection(Box::new( + connection.clone(), + )), ) }) .is_err() diff --git a/crates/nostr_signer/src/capability.rs b/crates/nostr_signer/src/capability.rs @@ -28,8 +28,8 @@ pub struct RadrootsNostrRemoteSessionSignerCapability { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum RadrootsNostrSignerCapability { - LocalAccount(RadrootsNostrLocalSignerCapability), - RemoteSession(RadrootsNostrRemoteSessionSignerCapability), + LocalAccount(Box<RadrootsNostrLocalSignerCapability>), + RemoteSession(Box<RadrootsNostrRemoteSessionSignerCapability>), } fn public_identity_eq(left: &RadrootsIdentityPublic, right: &RadrootsIdentityPublic) -> bool { @@ -92,14 +92,14 @@ impl RadrootsNostrSignerCapability { pub fn local_account(&self) -> Option<&RadrootsNostrLocalSignerCapability> { match self { - Self::LocalAccount(capability) => Some(capability), + Self::LocalAccount(capability) => Some(capability.as_ref()), Self::RemoteSession(_) => None, } } pub fn remote_session(&self) -> Option<&RadrootsNostrRemoteSessionSignerCapability> { match self { - Self::RemoteSession(capability) => Some(capability), + Self::RemoteSession(capability) => Some(capability.as_ref()), Self::LocalAccount(_) => None, } } @@ -130,8 +130,12 @@ impl Eq for RadrootsNostrRemoteSessionSignerCapability {} impl PartialEq for RadrootsNostrSignerCapability { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::LocalAccount(left), Self::LocalAccount(right)) => left == right, - (Self::RemoteSession(left), Self::RemoteSession(right)) => left == right, + (Self::LocalAccount(left), Self::LocalAccount(right)) => { + left.as_ref() == right.as_ref() + } + (Self::RemoteSession(left), Self::RemoteSession(right)) => { + left.as_ref() == right.as_ref() + } _ => false, } } @@ -153,9 +157,9 @@ impl From<&RadrootsNostrSignerConnectionRecord> for RadrootsNostrRemoteSessionSi impl RadrootsNostrSignerConnectionRecord { pub fn remote_session_capability(&self) -> RadrootsNostrSignerCapability { - RadrootsNostrSignerCapability::RemoteSession( + RadrootsNostrSignerCapability::RemoteSession(Box::new( RadrootsNostrRemoteSessionSignerCapability::from(self), - ) + )) } } @@ -184,12 +188,13 @@ mod tests { #[test] fn local_capability_reports_secret_backing_and_public_identity() { let public_identity = fixture_alice_identity(); - let capability = - RadrootsNostrSignerCapability::LocalAccount(RadrootsNostrLocalSignerCapability::new( + let capability = RadrootsNostrSignerCapability::LocalAccount(Box::new( + RadrootsNostrLocalSignerCapability::new( public_identity.id.clone(), public_identity.clone(), RadrootsNostrLocalSignerAvailability::SecretBacked, - )); + ), + )); assert_public_identity_matches(capability.public_identity(), &public_identity); assert!( @@ -330,16 +335,16 @@ mod tests { assert_ne!(remote, remote_changed); assert_eq!( - RadrootsNostrSignerCapability::LocalAccount(local.clone()), - RadrootsNostrSignerCapability::LocalAccount(local_same) + RadrootsNostrSignerCapability::LocalAccount(Box::new(local.clone())), + RadrootsNostrSignerCapability::LocalAccount(Box::new(local_same)) ); assert_eq!( - RadrootsNostrSignerCapability::RemoteSession(remote.clone()), - RadrootsNostrSignerCapability::RemoteSession(remote) + RadrootsNostrSignerCapability::RemoteSession(Box::new(remote.clone())), + RadrootsNostrSignerCapability::RemoteSession(Box::new(remote)) ); assert_ne!( - RadrootsNostrSignerCapability::LocalAccount(local), - RadrootsNostrSignerCapability::RemoteSession(remote_changed) + RadrootsNostrSignerCapability::LocalAccount(Box::new(local)), + RadrootsNostrSignerCapability::RemoteSession(Box::new(remote_changed)) ); } diff --git a/crates/nostr_signer/src/evaluation.rs b/crates/nostr_signer/src/evaluation.rs @@ -14,7 +14,7 @@ use radroots_nostr_connect::prelude::{ #[derive(Debug, Clone)] pub enum RadrootsNostrSignerSessionLookup { None, - Connection(RadrootsNostrSignerConnectionRecord), + Connection(Box<RadrootsNostrSignerConnectionRecord>), Ambiguous(Vec<RadrootsNostrSignerConnectionRecord>), } @@ -27,7 +27,7 @@ pub struct RadrootsNostrSignerConnectProposal { #[derive(Debug, Clone)] pub enum RadrootsNostrSignerConnectEvaluation { - ExistingConnection(RadrootsNostrSignerConnectionRecord), + ExistingConnection(Box<RadrootsNostrSignerConnectionRecord>), RegistrationRequired(RadrootsNostrSignerConnectProposal), } diff --git a/crates/nostr_signer/src/manager.rs b/crates/nostr_signer/src/manager.rs @@ -172,22 +172,24 @@ impl RadrootsNostrSignerManager { client_public_key: &PublicKey, connect_secret: Option<&str>, ) -> Result<RadrootsNostrSignerSessionLookup, RadrootsNostrSignerError> { - if let Some(connect_secret) = connect_secret { - if let Some(connection) = self.find_connection_by_connect_secret(connect_secret)? { - if &connection.client_public_key != client_public_key { - return Err(RadrootsNostrSignerError::InvalidState( - "connect secret is bound to a different client public key".into(), - )); - } - return Ok(RadrootsNostrSignerSessionLookup::Connection(connection)); + if let Some(connect_secret) = connect_secret + && let Some(connection) = self.find_connection_by_connect_secret(connect_secret)? + { + if &connection.client_public_key != client_public_key { + return Err(RadrootsNostrSignerError::InvalidState( + "connect secret is bound to a different client public key".into(), + )); } + return Ok(RadrootsNostrSignerSessionLookup::Connection(Box::new( + connection, + ))); } let mut matches = self.find_connections_by_client_public_key(client_public_key)?; matches.retain(|record| !record.is_terminal()); Ok(match matches.len() { 0 => RadrootsNostrSignerSessionLookup::None, - 1 => RadrootsNostrSignerSessionLookup::Connection(matches.remove(0)), + 1 => RadrootsNostrSignerSessionLookup::Connection(Box::new(matches.remove(0))), _ => RadrootsNostrSignerSessionLookup::Ambiguous(matches), }) } @@ -217,7 +219,7 @@ impl RadrootsNostrSignerManager { )); } return Ok(RadrootsNostrSignerConnectEvaluation::ExistingConnection( - connection, + Box::new(connection), )); } @@ -272,13 +274,13 @@ impl RadrootsNostrSignerManager { .connect_secret .as_deref() .and_then(RadrootsNostrSignerConnectSecretHash::from_secret); - if let Some(secret_hash) = connect_secret_hash.as_ref() { - if state.connections.iter().any(|record| { + if let Some(secret_hash) = connect_secret_hash.as_ref() + && state.connections.iter().any(|record| { record.connect_secret_hash.as_ref() == Some(secret_hash) && (!record.is_terminal() || record.connect_secret_is_consumed()) - }) { - return Err(RadrootsNostrSignerError::ConnectSecretAlreadyInUse); - } + }) + { + return Err(RadrootsNostrSignerError::ConnectSecretAlreadyInUse); } if state.connections.iter().any(|record| { @@ -923,13 +925,8 @@ impl RadrootsNostrSignerManager { .write() .map_err(|_| RadrootsNostrSignerError::Store("signer state lock poisoned".into()))?; let mut next = guard.clone(); - let value = match update(&mut next) { - Ok(value) => value, - Err(err) => return Err(err), - }; - if let Err(err) = self.store.save(&next) { - return Err(err); - } + let value = update(&mut next)?; + self.store.save(&next)?; *guard = next; Ok(value) } @@ -1257,7 +1254,7 @@ mod tests { lookup: RadrootsNostrSignerSessionLookup, ) -> RadrootsNostrSignerConnectionRecord { match lookup { - RadrootsNostrSignerSessionLookup::Connection(found) => found, + RadrootsNostrSignerSessionLookup::Connection(found) => *found, other => panic!("unexpected lookup result: {other:?}"), } } @@ -1277,7 +1274,7 @@ mod tests { evaluation: RadrootsNostrSignerConnectEvaluation, ) -> RadrootsNostrSignerConnectionRecord { match evaluation { - RadrootsNostrSignerConnectEvaluation::ExistingConnection(found) => found, + RadrootsNostrSignerConnectEvaluation::ExistingConnection(found) => *found, other => panic!("unexpected existing connect result: {other:?}"), } } diff --git a/crates/nostr_signer/src/model.rs b/crates/nostr_signer/src/model.rs @@ -67,8 +67,9 @@ pub enum RadrootsNostrSignerRequestDecision { Challenged, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum RadrootsNostrSignerAuthState { + #[default] NotRequired, Pending, Authorized, @@ -442,12 +443,6 @@ impl RadrootsNostrSignerAuthorizationOutcome { } } -impl Default for RadrootsNostrSignerAuthState { - fn default() -> Self { - Self::NotRequired - } -} - impl RadrootsNostrSignerPermissionGrant { pub fn new(permission: RadrootsNostrConnectPermission, granted_at_unix: u64) -> Self { Self { diff --git a/crates/nostr_signer/src/nip46.rs b/crates/nostr_signer/src/nip46.rs @@ -121,7 +121,7 @@ pub struct RadrootsNostrSignerNip46Handler<B, P, S> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum RadrootsNostrSignerHandledRequest { Respond { - response: RadrootsNostrConnectResponse, + response: Box<RadrootsNostrConnectResponse>, connection_id: Option<RadrootsNostrSignerConnectionId>, consume_connect_secret_for: Option<RadrootsNostrSignerConnectionId>, }, @@ -139,7 +139,7 @@ enum RadrootsNostrSignerPreparedRequestEvaluation { reason: String, audit: RadrootsNostrSignerRequestAuditRecord, }, - Evaluation(RadrootsNostrSignerRequestEvaluation), + Evaluation(Box<RadrootsNostrSignerRequestEvaluation>), } impl<S: RadrootsNostrSignerNip46Signer> RadrootsNostrSignerNip46Codec<S> { @@ -352,36 +352,32 @@ where secret: Option<String>, ) -> Result<RadrootsNostrSignerHandledRequestOutcome, RadrootsNostrSignerError> { let connect_decision = self.policy.connect_decision(&client_public_key); - if let Some(connect_secret) = secret.as_deref() { - if let Some(connection) = self + if let Some(connect_secret) = secret.as_deref() + && let Some(connection) = self .backend .find_connection_by_connect_secret(connect_secret)? - { - if connection.connect_secret_is_consumed() { - return Ok(RadrootsNostrSignerHandledRequestOutcome::ignore()); - } - } + && connection.connect_secret_is_consumed() + { + return Ok(RadrootsNostrSignerHandledRequestOutcome::ignore()); } if !matches!( connect_decision, RadrootsNostrSignerNip46ConnectDecision::Deny - ) { - if let Some(reason) = self - .policy - .connect_rate_limit_denied_reason(&client_public_key) - { - return Ok(RadrootsNostrSignerHandledRequestOutcome::respond( - RadrootsNostrConnectResponse::Error { - result: None, - error: reason, - }, - )); - } + ) && let Some(reason) = self + .policy + .connect_rate_limit_denied_reason(&client_public_key) + { + return Ok(RadrootsNostrSignerHandledRequestOutcome::respond( + RadrootsNostrConnectResponse::Error { + result: None, + error: reason, + }, + )); } let evaluation = self .backend - .evaluate_connect_request(client_public_key.clone(), request)?; + .evaluate_connect_request(client_public_key, request)?; match evaluation { RadrootsNostrSignerConnectEvaluation::ExistingConnection(connection) => { @@ -467,6 +463,7 @@ where )) } RadrootsNostrSignerPreparedRequestEvaluation::Evaluation(evaluation) => { + let evaluation = *evaluation; let audit = evaluation.audit.clone(); let response_hint = match &evaluation.action { RadrootsNostrSignerRequestAction::Allowed { response_hint, .. } => { @@ -514,6 +511,7 @@ where )) } RadrootsNostrSignerPreparedRequestEvaluation::Evaluation(evaluation) => { + let evaluation = *evaluation; Ok(RadrootsNostrSignerHandledRequestOutcome::new( self.handled_request_for_authorized_action( &evaluation.connection, @@ -553,6 +551,7 @@ where )) } RadrootsNostrSignerPreparedRequestEvaluation::Evaluation(evaluation) => { + let evaluation = *evaluation; Ok(RadrootsNostrSignerHandledRequestOutcome::new( self.handled_request_for_authorized_action( &evaluation.connection, @@ -649,8 +648,10 @@ where } Ok(RadrootsNostrSignerPreparedRequestEvaluation::Evaluation( - self.backend - .evaluate_request(&connection.connection_id, request_message)?, + Box::new( + self.backend + .evaluate_request(&connection.connection_id, request_message)?, + ), )) } @@ -663,7 +664,7 @@ where > { Ok( match self.backend.lookup_session(&client_public_key, None)? { - RadrootsNostrSignerSessionLookup::Connection(connection) => Ok(connection), + RadrootsNostrSignerSessionLookup::Connection(connection) => Ok(*connection), RadrootsNostrSignerSessionLookup::None => { Err(RadrootsNostrConnectResponse::Error { result: None, @@ -691,7 +692,7 @@ impl RadrootsNostrSignerHandledRequest { response: RadrootsNostrConnectResponse, ) -> Self { Self::Respond { - response, + response: Box::new(response), connection_id, consume_connect_secret_for: None, } @@ -709,7 +710,7 @@ impl RadrootsNostrSignerHandledRequest { response, connection_id, consume_connect_secret_for, - } => Some((response, connection_id, consume_connect_secret_for)), + } => Some((*response, connection_id, consume_connect_secret_for)), Self::Ignore => None, } } @@ -741,10 +742,10 @@ pub fn connect_response_outcome( ) -> RadrootsNostrSignerHandledRequest { let consume_connect_secret_for = secret.as_ref().map(|_| connection.connection_id.clone()); RadrootsNostrSignerHandledRequest::Respond { - response: match secret { + response: Box::new(match secret { Some(secret) => RadrootsNostrConnectResponse::ConnectSecretEcho(secret), None => RadrootsNostrConnectResponse::ConnectAcknowledged, - }, + }), connection_id: Some(connection.connection_id.clone()), consume_connect_secret_for, } @@ -1012,7 +1013,7 @@ mod tests { assert!(connect.audit.is_none()); match connect.handled_request { RadrootsNostrSignerHandledRequest::Respond { response, .. } => { - assert_eq!(response, RadrootsNostrConnectResponse::ConnectAcknowledged); + assert_eq!(*response, RadrootsNostrConnectResponse::ConnectAcknowledged); } other => panic!("unexpected connect outcome: {other:?}"), } @@ -1028,7 +1029,7 @@ mod tests { .expect("ping outcome"); match ping.handled_request { RadrootsNostrSignerHandledRequest::Respond { response, .. } => { - assert_eq!(response, RadrootsNostrConnectResponse::Pong); + assert_eq!(*response, RadrootsNostrConnectResponse::Pong); } other => panic!("unexpected ping outcome: {other:?}"), } diff --git a/crates/nostr_signer/src/store.rs b/crates/nostr_signer/src/store.rs @@ -231,9 +231,9 @@ impl RadrootsNostrSignerStore for RadrootsNostrSqliteSignerStore { })?; state.connections[index].auth_challenge = Some( RadrootsNostrSignerAuthChallenge::new(row.auth_url.as_str(), row.required_at_unix) - .and_then(|mut challenge| { + .map(|mut challenge| { challenge.authorized_at_unix = row.authorized_at_unix; - Ok(challenge) + challenge })?, ); } diff --git a/crates/outbox/src/store.rs b/crates/outbox/src/store.rs @@ -85,33 +85,32 @@ impl RadrootsOutbox { let accepted_quorum = target_relays.len() as i64; let mut tx = self.pool.begin().await?; - if let Some(idempotency_key) = input.idempotency_key.as_deref() { - if let Some(existing) = existing_idempotent_operation( + if let Some(idempotency_key) = input.idempotency_key.as_deref() + && let Some(existing) = existing_idempotent_operation( &mut tx, input.operation_kind.as_str(), input.draft.expected_pubkey.as_str(), idempotency_key, ) .await? - { - if existing.idempotency_digest != digest { - return Err(RadrootsOutboxError::IdempotencyConflict { - operation_kind: input.operation_kind, - expected_pubkey: input.draft.expected_pubkey, - idempotency_key: idempotency_key.to_owned(), - existing_digest: existing.idempotency_digest, - new_digest: digest, - }); - } - tx.commit().await?; - return Ok(RadrootsOutboxEnqueueReceipt { - status: RadrootsOutboxEnqueueStatus::Existing, - operation_id: existing.operation_id, - outbox_event_id: existing.outbox_event_id, - expected_event_id: existing.event_id, - idempotency_digest: digest, + { + if existing.idempotency_digest != digest { + return Err(RadrootsOutboxError::IdempotencyConflict { + operation_kind: input.operation_kind, + expected_pubkey: input.draft.expected_pubkey, + idempotency_key: idempotency_key.to_owned(), + existing_digest: existing.idempotency_digest, + new_digest: digest, }); } + tx.commit().await?; + return Ok(RadrootsOutboxEnqueueReceipt { + status: RadrootsOutboxEnqueueStatus::Existing, + operation_id: existing.operation_id, + outbox_event_id: existing.outbox_event_id, + expected_event_id: existing.event_id, + idempotency_digest: digest, + }); } let operation = sqlx::query( diff --git a/crates/replica_db/src/backup.rs b/crates/replica_db/src/backup.rs @@ -196,7 +196,7 @@ fn insert_row( placeholders ); - let binds: Vec<Value> = cols.values().map(|v| utils::to_db_bind_value(*v)).collect(); + let binds: Vec<Value> = cols.values().map(|v| utils::to_db_bind_value(v)).collect(); let params_json = Value::Array(binds).to_string(); executor.exec(&sql, &params_json)?; Ok(()) diff --git a/crates/replica_db_schema/src/lib.rs b/crates/replica_db_schema/src/lib.rs @@ -4,7 +4,7 @@ pub use models::*; #[cfg_attr(not(test), allow(dead_code))] fn coverage_branch_probe(value: Option<&str>) -> usize { match value { - Some(raw) if raw.is_empty() => 0, + Some("") => 0, Some(raw) => raw.len(), None => 0, } diff --git a/crates/replica_db_schema/src/models/gcs_location.rs b/crates/replica_db_schema/src/models/gcs_location.rs @@ -194,7 +194,7 @@ pub type IGcsLocationFindOneResolve = IResult<Option<GcsLocation>>; #[serde(untagged)] pub enum IGcsLocationFindMany { Filter { - filter: Option<IGcsLocationFieldsFilter>, + filter: Box<Option<IGcsLocationFieldsFilter>>, }, Rel { rel: GcsLocationFindManyRel, diff --git a/crates/replica_db_schema/src/models/nostr_profile.rs b/crates/replica_db_schema/src/models/nostr_profile.rs @@ -131,7 +131,7 @@ pub type INostrProfileFindOneResolve = IResult<Option<NostrProfile>>; #[serde(untagged)] pub enum INostrProfileFindMany { Filter { - filter: Option<INostrProfileFieldsFilter>, + filter: Box<Option<INostrProfileFieldsFilter>>, }, Rel { rel: NostrProfileFindManyRel, diff --git a/crates/replica_db_schema/src/models/nostr_relay.rs b/crates/replica_db_schema/src/models/nostr_relay.rs @@ -127,7 +127,7 @@ pub type INostrRelayFindOneResolve = IResult<Option<NostrRelay>>; #[serde(untagged)] pub enum INostrRelayFindMany { Filter { - filter: Option<INostrRelayFieldsFilter>, + filter: Box<Option<INostrRelayFieldsFilter>>, }, Rel { rel: NostrRelayFindManyRel, diff --git a/crates/replica_sync/src/emit.rs b/crates/replica_sync/src/emit.rs @@ -711,21 +711,21 @@ fn gcs_location_to_event( } fn parse_point(value: &str, lat: f64, lng: f64) -> RadrootsGeoJsonPoint { - if !value.trim().is_empty() { - if let Ok(parsed) = serde_json::from_str::<RadrootsGeoJsonPoint>(value) { - return parsed; - } + if !value.trim().is_empty() + && let Ok(parsed) = serde_json::from_str::<RadrootsGeoJsonPoint>(value) + { + return parsed; } geojson_point_from_lat_lng(lat, lng) } fn parse_polygon(value: &str, lat: f64, lng: f64) -> RadrootsGeoJsonPolygon { - if !value.trim().is_empty() { - if let Ok(parsed) = serde_json::from_str::<RadrootsGeoJsonPolygon>(value) { - if !parsed.coordinates.is_empty() && !parsed.coordinates[0].is_empty() { - return parsed; - } - } + if !value.trim().is_empty() + && let Ok(parsed) = serde_json::from_str::<RadrootsGeoJsonPolygon>(value) + && !parsed.coordinates.is_empty() + && !parsed.coordinates[0].is_empty() + { + return parsed; } geojson_polygon_circle_wgs84(lat, lng, 100.0, 64) } @@ -775,9 +775,10 @@ fn profile_event( let content = serialize_profile_content(&profile_event)?; let mut tags = Vec::new(); if let Some(profile_type) = profile_type { - let mut tag = Vec::with_capacity(2); - tag.push(RADROOTS_PROFILE_TYPE_TAG_KEY.to_string()); - tag.push(radroots_profile_type_tag_value(profile_type).to_string()); + let tag = vec![ + RADROOTS_PROFILE_TYPE_TAG_KEY.to_string(), + radroots_profile_type_tag_value(profile_type).to_string(), + ]; tags.push(tag); } Ok(RadrootsReplicaEventDraft { diff --git a/crates/replica_sync/src/error.rs b/crates/replica_sync/src/error.rs @@ -19,7 +19,7 @@ pub enum RadrootsReplicaEventsError { impl fmt::Display for RadrootsReplicaEventsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Sql(err) => write!(f, "replica_sync.sql: {}", err.err.to_string()), + Self::Sql(err) => write!(f, "replica_sync.sql: {}", err.err), Self::Encode(err) => write!(f, "replica_sync.encode: {err}"), Self::Parse(err) => write!(f, "replica_sync.parse: {err}"), Self::InvalidSelector(msg) => write!(f, "replica_sync.selector: {msg}"), diff --git a/crates/replica_sync/src/event_head.rs b/crates/replica_sync/src/event_head.rs @@ -78,7 +78,7 @@ pub(crate) fn event_content_hash_fail_next() { pub fn tag_value<'a>(tags: &'a [Vec<String>], key: &str) -> Option<&'a str> { tags.iter() - .find(|tag| tag.get(0).map(|v| v.as_str()) == Some(key)) + .find(|tag| tag.first().map(|v| v.as_str()) == Some(key)) .and_then(|tag| tag.get(1)) .map(|value| value.as_str()) } diff --git a/crates/replica_sync/src/ingest.rs b/crates/replica_sync/src/ingest.rs @@ -167,7 +167,7 @@ pub fn radroots_replica_ingest_event_with_factory( )); } - let outcome = match ingest_event_inner(exec, event, factory) { + match ingest_event_inner(exec, event, factory) { Ok(outcome) => { if let Err(err) = exec.commit() { return Err(RadrootsReplicaEventsError::from( @@ -180,9 +180,7 @@ pub fn radroots_replica_ingest_event_with_factory( let _ = exec.rollback(); Err(err) } - }; - - outcome + } } fn ingest_event_inner( @@ -331,7 +329,7 @@ fn ingest_farm_event( let location = farm.location.clone(); let (location_primary, location_city, location_region, location_country) = unpack_farm_location_strings(location.as_ref()); - let farm_id = if let Some(row) = existing.results.get(0) { + let farm_id = if let Some(row) = existing.results.first() { let fields = IFarmFieldsPartial { d_tag: Some(Value::from(farm.d_tag.clone())), pubkey: Some(Value::from(event.author.clone())), @@ -413,7 +411,7 @@ fn ingest_plot_event( let location = plot.location.clone(); let (location_primary, location_city, location_region, location_country) = unpack_plot_location_strings(location.as_ref()); - let plot_id = if let Some(row) = existing.results.get(0) { + let plot_id = if let Some(row) = existing.results.first() { let fields = IPlotFieldsPartial { d_tag: Some(Value::from(plot.d_tag.clone())), farm_id: Some(Value::from(farm.id.clone())), @@ -1112,16 +1110,16 @@ fn upsert_farm_location( factory: &dyn RadrootsReplicaIdFactory, ) -> Result<(), RadrootsReplicaEventsError> { clear_farm_locations(exec, farm_id)?; - if let Some(location) = location { - if let Some(gcs) = location.gcs { - let gcs_id = create_gcs_location(exec, gcs, factory)?; - let fields = IFarmGcsLocationFields { - farm_id: farm_id.to_string(), - gcs_location_id: gcs_id, - role: ROLE_PRIMARY.to_string(), - }; - let _ = farm_gcs_location::create(exec, &fields)?; - } + if let Some(location) = location + && let Some(gcs) = location.gcs + { + let gcs_id = create_gcs_location(exec, gcs, factory)?; + let fields = IFarmGcsLocationFields { + farm_id: farm_id.to_string(), + gcs_location_id: gcs_id, + role: ROLE_PRIMARY.to_string(), + }; + let _ = farm_gcs_location::create(exec, &fields)?; } Ok(()) } diff --git a/crates/runtime/src/error.rs b/crates/runtime/src/error.rs @@ -68,7 +68,7 @@ pub enum RuntimeProtectedFileError { #[derive(Debug, Error)] pub enum RuntimeError { #[error(transparent)] - Config(#[from] RuntimeConfigError), + Config(#[from] Box<RuntimeConfigError>), #[cfg(feature = "cli")] #[error(transparent)] @@ -78,6 +78,12 @@ pub enum RuntimeError { Tracing(#[from] RuntimeTracingError), } +impl From<RuntimeConfigError> for RuntimeError { + fn from(value: RuntimeConfigError) -> Self { + Self::Config(Box::new(value)) + } +} + #[cfg(test)] mod tests { use super::{RuntimeConfigError, RuntimeError, RuntimeProtectedFileError, RuntimeTracingError}; diff --git a/crates/runtime_manager/src/managed.rs b/crates/runtime_manager/src/managed.rs @@ -190,10 +190,10 @@ pub fn load_management_context_with_selection( load_management_context(contract, resolver, selection.profile, &overrides) } -pub fn active_management_mode_for_profile<'a>( - contract: &'a RadrootsRuntimeManagementContract, +pub fn active_management_mode_for_profile( + contract: &RadrootsRuntimeManagementContract, profile: RadrootsPathProfile, -) -> Result<&'a str, RadrootsRuntimeManagerError> { +) -> Result<&str, RadrootsRuntimeManagerError> { let profile_id = profile.to_string(); contract .mode diff --git a/crates/secret_vault/src/selection.rs b/crates/secret_vault/src/selection.rs @@ -34,10 +34,10 @@ impl RadrootsSecretBackendSelection { }); } - if let RadrootsSecretBackend::HostVault(policy) = self.primary { - if availability.host_vault.available { - availability.host_vault.validate(policy)?; - } + if let RadrootsSecretBackend::HostVault(policy) = self.primary + && availability.host_vault.available + { + availability.host_vault.validate(policy)?; } match self.fallback { diff --git a/crates/simplex_agent_store/src/store.rs b/crates/simplex_agent_store/src/store.rs @@ -264,7 +264,7 @@ struct RadrootsSimplexAgentMessageReceiptSnapshot { receipt_info: Vec<u8>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct RadrootsSimplexAgentStore { next_connection_sequence: u64, next_command_sequence: u64, @@ -274,19 +274,6 @@ pub struct RadrootsSimplexAgentStore { persistence_path: Option<PathBuf>, } -impl Default for RadrootsSimplexAgentStore { - fn default() -> Self { - Self { - next_connection_sequence: 0, - next_command_sequence: 0, - connections: BTreeMap::new(), - pending_commands: BTreeMap::new(), - #[cfg(feature = "std")] - persistence_path: None, - } - } -} - impl RadrootsSimplexAgentStore { pub fn new() -> Self { Self::default() @@ -296,9 +283,10 @@ impl RadrootsSimplexAgentStore { pub fn open(path: impl AsRef<Path>) -> Result<Self, RadrootsSimplexAgentStoreError> { let path = path.as_ref().to_path_buf(); if !path.exists() { - let mut store = Self::default(); - store.persistence_path = Some(path); - return Ok(store); + return Ok(Self { + persistence_path: Some(path), + ..Default::default() + }); } let raw = fs::read(&path).map_err(|error| { diff --git a/crates/simplex_chat_proto/src/codec.rs b/crates/simplex_chat_proto/src/codec.rs @@ -260,9 +260,9 @@ fn decode_wire_message( "x.info.probe.ok" => { RadrootsSimplexChatEvent::InfoProbeOk(decode_probe_event(wire.params)?) } - "x.msg.new" => RadrootsSimplexChatEvent::MsgNew(RadrootsSimplexChatMsgNewEvent { + "x.msg.new" => RadrootsSimplexChatEvent::MsgNew(Box::new(RadrootsSimplexChatMsgNewEvent { container: decode_message_container(wire.params)?, - }), + })), "x.msg.file.descr" => { RadrootsSimplexChatEvent::MsgFileDescr(decode_file_description_event(wire.params)?) } @@ -613,10 +613,10 @@ fn decode_message_container( mut params: RadrootsSimplexChatObject, ) -> Result<RadrootsSimplexChatMessageContainer, RadrootsSimplexChatProtoError> { let kind = if let Some(value) = params.remove("quote") { - RadrootsSimplexChatContainerKind::Quote( + RadrootsSimplexChatContainerKind::Quote(Box::new( serde_json::from_value::<RadrootsSimplexChatQuotedMessage>(value) .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string()))?, - ) + )) } else if let Some(value) = params.remove("parent") { RadrootsSimplexChatContainerKind::Comment( serde_json::from_value::<RadrootsSimplexChatMessageRef>(value) diff --git a/crates/simplex_chat_proto/src/model.rs b/crates/simplex_chat_proto/src/model.rs @@ -513,7 +513,7 @@ impl<'de> Deserialize<'de> for RadrootsSimplexChatScope { #[derive(Debug, Clone, PartialEq)] pub enum RadrootsSimplexChatContainerKind { Simple, - Quote(RadrootsSimplexChatQuotedMessage), + Quote(Box<RadrootsSimplexChatQuotedMessage>), Comment(RadrootsSimplexChatMessageRef), Forward(RadrootsSimplexChatForwardMarker), } @@ -626,7 +626,7 @@ pub enum RadrootsSimplexChatEvent { InfoProbe(RadrootsSimplexChatProbeEvent), InfoProbeCheck(RadrootsSimplexChatProbeCheckEvent), InfoProbeOk(RadrootsSimplexChatProbeEvent), - MsgNew(RadrootsSimplexChatMsgNewEvent), + MsgNew(Box<RadrootsSimplexChatMsgNewEvent>), MsgFileDescr(RadrootsSimplexChatFileDescriptionEvent), MsgUpdate(RadrootsSimplexChatMsgUpdateEvent), MsgDel(RadrootsSimplexChatDeleteEvent), diff --git a/crates/simplex_smp_crypto/src/auth.rs b/crates/simplex_smp_crypto/src/auth.rs @@ -195,7 +195,9 @@ pub fn decode_x25519_public_key_x509( let raw = encoded .strip_prefix(X25519_SPKI_DER_PREFIX) .or_else(|| encoded.strip_prefix(X25519_SPKI_DER_PREFIX_WRAPPED)) - .ok_or_else(|| RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(encoded.len()))?; + .ok_or(RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength( + encoded.len(), + ))?; let key: [u8; 32] = raw .try_into() .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(encoded.len()))?; diff --git a/crates/simplex_smp_crypto/src/message.rs b/crates/simplex_smp_crypto/src/message.rs @@ -16,6 +16,12 @@ const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_KEY_LENGTH: usize = 32; const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_INIT_OUTPUT_LENGTH: usize = 64; const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_STEP_OUTPUT_LENGTH: usize = 88; +type RadrootsSimplexSmpSecretBoxKeyAndNonce = (Vec<u8>, [u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH]); +type RadrootsSimplexSmpSecretBoxChainStep = ( + RadrootsSimplexSmpSecretBoxKeyAndNonce, + RadrootsSimplexSmpSecretBoxChainKey, +); + #[derive(Debug, Clone, PartialEq, Eq)] pub struct RadrootsSimplexSmpX25519Keypair { pub public_key: Vec<u8>, @@ -98,13 +104,7 @@ pub fn init_secretbox_chain( pub fn advance_secretbox_chain( chain_key: &RadrootsSimplexSmpSecretBoxChainKey, -) -> Result< - ( - (Vec<u8>, [u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH]), - RadrootsSimplexSmpSecretBoxChainKey, - ), - RadrootsSimplexSmpCryptoError, -> { +) -> Result<RadrootsSimplexSmpSecretBoxChainStep, RadrootsSimplexSmpCryptoError> { let output = hkdf_expand( b"", &chain_key.bytes, @@ -228,11 +228,8 @@ fn cipher(shared_secret: &[u8]) -> Result<XSalsa20Poly1305, RadrootsSimplexSmpCr shared_secret.len(), )); } - Ok( - XSalsa20Poly1305::new_from_slice(shared_secret).map_err(|_| { - RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(shared_secret.len()) - })?, - ) + XSalsa20Poly1305::new_from_slice(shared_secret) + .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(shared_secret.len())) } fn nonce_array( diff --git a/crates/simplex_smp_proto/src/wire.rs b/crates/simplex_smp_proto/src/wire.rs @@ -426,6 +426,13 @@ pub struct RadrootsSimplexSmpBrokerTransmission { pub message: RadrootsSimplexSmpBrokerMessage, } +type RadrootsSimplexSmpDecodedTransmission<'a> = ( + Vec<u8>, + Option<RadrootsSimplexSmpCorrelationId>, + Vec<u8>, + &'a [u8], +); + impl RadrootsSimplexSmpCommand { pub fn encode(&self) -> Result<Vec<u8>, RadrootsSimplexSmpProtoError> { self.encode_for_version(RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION) @@ -1327,15 +1334,7 @@ fn encode_transmission( fn decode_transmission( transport_version: u16, bytes: &[u8], -) -> Result< - ( - Vec<u8>, - Option<RadrootsSimplexSmpCorrelationId>, - Vec<u8>, - &[u8], - ), - RadrootsSimplexSmpProtoError, -> { +) -> Result<RadrootsSimplexSmpDecodedTransmission<'_>, RadrootsSimplexSmpProtoError> { let mut cursor = Cursor::new(bytes); let authorization = cursor.read_short_bytes()?; if transport_version >= RADROOTS_SIMPLEX_SMP_SERVICE_CERTS_TRANSPORT_VERSION diff --git a/crates/simplex_smp_transport/src/client.rs b/crates/simplex_smp_transport/src/client.rs @@ -193,18 +193,18 @@ fn encode_live_transport_block( session: &mut RadrootsSimplexSmpLiveSession, block: &RadrootsSimplexSmpTransportBlock, ) -> Result<Vec<u8>, RadrootsSimplexSmpTransportError> { - if session.transport_version >= RADROOTS_SIMPLEX_SMP_ENCRYPTED_BLOCK_TRANSPORT_VERSION { - if let Some(chain_key) = session.send_chain_key.as_ref() { - let ((secretbox_key, nonce), next_chain_key) = advance_secretbox_chain(chain_key)?; - session.send_chain_key = Some(next_chain_key); - return encrypt_padded( - &secretbox_key, - &nonce, - &block.encode_payload()?, - RADROOTS_SIMPLEX_SMP_TRANSPORT_BLOCK_SIZE - 16, - ) - .map_err(Into::into); - } + if session.transport_version >= RADROOTS_SIMPLEX_SMP_ENCRYPTED_BLOCK_TRANSPORT_VERSION + && let Some(chain_key) = session.send_chain_key.as_ref() + { + let ((secretbox_key, nonce), next_chain_key) = advance_secretbox_chain(chain_key)?; + session.send_chain_key = Some(next_chain_key); + return encrypt_padded( + &secretbox_key, + &nonce, + &block.encode_payload()?, + RADROOTS_SIMPLEX_SMP_TRANSPORT_BLOCK_SIZE - 16, + ) + .map_err(Into::into); } block.encode() } @@ -213,50 +213,46 @@ fn decode_live_transport_block( session: &mut RadrootsSimplexSmpLiveSession, bytes: &[u8], ) -> Result<RadrootsSimplexSmpTransportBlock, RadrootsSimplexSmpTransportError> { - if session.transport_version >= RADROOTS_SIMPLEX_SMP_ENCRYPTED_BLOCK_TRANSPORT_VERSION { - if let Some(chain_key) = session.receive_chain_key.as_ref() { - let ((secretbox_key, nonce), next_chain_key) = advance_secretbox_chain(chain_key)?; - match radroots_simplex_smp_crypto::prelude::decrypt_padded( - &secretbox_key, - &nonce, - bytes, - ) { - Ok(payload) => { - session.receive_chain_key = Some(next_chain_key); - debug_sha256_label("live-response-payload", &payload); - return RadrootsSimplexSmpTransportBlock::from_payload(&payload); + if session.transport_version >= RADROOTS_SIMPLEX_SMP_ENCRYPTED_BLOCK_TRANSPORT_VERSION + && let Some(chain_key) = session.receive_chain_key.as_ref() + { + let ((secretbox_key, nonce), next_chain_key) = advance_secretbox_chain(chain_key)?; + match radroots_simplex_smp_crypto::prelude::decrypt_padded(&secretbox_key, &nonce, bytes) { + Ok(payload) => { + session.receive_chain_key = Some(next_chain_key); + debug_sha256_label("live-response-payload", &payload); + return RadrootsSimplexSmpTransportBlock::from_payload(&payload); + } + Err(error) => { + if transport_debug_enabled() { + eprintln!("[simplex-smp-transport] live response decrypt failed: {error}"); + debug_sha256_label("live-response-ciphertext", bytes); } - Err(error) => { - if transport_debug_enabled() { - eprintln!("[simplex-smp-transport] live response decrypt failed: {error}"); - debug_sha256_label("live-response-ciphertext", bytes); - } - if let Some(send_chain_key) = session.send_chain_key.as_ref() { - let ((alt_secretbox_key, alt_nonce), _) = - advance_secretbox_chain(send_chain_key)?; - if radroots_simplex_smp_crypto::prelude::decrypt_padded( - &alt_secretbox_key, - &alt_nonce, - bytes, - ) - .is_ok() - { - return Err(RadrootsSimplexSmpTransportError::InvalidServerAddress( - "server response decrypted with the outbound chain key; live SMP block direction is assigned incorrectly".into(), - )); - } - } - debug_probe_transport_candidates(session, bytes); - if let Ok(block) = RadrootsSimplexSmpTransportBlock::decode(bytes) { + if let Some(send_chain_key) = session.send_chain_key.as_ref() { + let ((alt_secretbox_key, alt_nonce), _) = + advance_secretbox_chain(send_chain_key)?; + if radroots_simplex_smp_crypto::prelude::decrypt_padded( + &alt_secretbox_key, + &alt_nonce, + bytes, + ) + .is_ok() + { return Err(RadrootsSimplexSmpTransportError::InvalidServerAddress( - format!( - "server returned plaintext SMP block while encrypted transport was expected: {:?}", - block.transmissions.first().map(|t| &t[..t.len().min(8)]) - ), + "server response decrypted with the outbound chain key; live SMP block direction is assigned incorrectly".into(), )); } - return Err(error.into()); } + debug_probe_transport_candidates(session, bytes); + if let Ok(block) = RadrootsSimplexSmpTransportBlock::decode(bytes) { + return Err(RadrootsSimplexSmpTransportError::InvalidServerAddress( + format!( + "server returned plaintext SMP block while encrypted transport was expected: {:?}", + block.transmissions.first().map(|t| &t[..t.len().min(8)]) + ), + )); + } + return Err(error.into()); } } } @@ -670,7 +666,7 @@ fn matching_server_identity( } } Err(RadrootsSimplexSmpTransportError::ServerIdentityMismatch { - expected: expected_identity.into(), + expected: expected_identity, actual: chain .first() .map(|certificate| server_identity_from_certificate(certificate.as_ref())) diff --git a/crates/sp1_host_trade/src/lib.rs b/crates/sp1_host_trade/src/lib.rs @@ -1543,18 +1543,16 @@ fn witness_with_sp1_identity( if let (Some(existing), Some(actual)) = ( witness.sp1_program_hash.as_deref(), sp1_program_hash.as_deref(), - ) { - if existing != actual { - return Err(RadrootsSp1TradeHostError::Sp1ProgramHashMismatch); - } + ) && existing != actual + { + return Err(RadrootsSp1TradeHostError::Sp1ProgramHashMismatch); } if let (Some(existing), Some(actual)) = ( witness.sp1_verifying_key_hash.as_deref(), sp1_verifying_key_hash.as_deref(), - ) { - if existing != actual { - return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch); - } + ) && existing != actual + { + return Err(RadrootsSp1TradeHostError::Sp1VerifyingKeyHashMismatch); } let mut bound = witness.clone(); @@ -1649,7 +1647,7 @@ fn decode_sp1_proof_artifact( fn decode_sp1_proof_envelope( envelope: &RadrootsSp1TradeProofEnvelope, ) -> Result<sp1_sdk::SP1ProofWithPublicValues, RadrootsSp1TradeHostError> { - let proof_bytes = proof_content_bytes_from_envelope(&envelope)?; + let proof_bytes = proof_content_bytes_from_envelope(envelope)?; bincode::deserialize::<sp1_sdk::SP1ProofWithPublicValues>(&proof_bytes) .map_err(|error| RadrootsSp1TradeHostError::Sp1ProofMaterialDecode(error.to_string())) } diff --git a/crates/sql_core/src/executor_embedded.rs b/crates/sql_core/src/executor_embedded.rs @@ -11,6 +11,12 @@ impl EmbeddedSqlExecutor { } } +impl Default for EmbeddedSqlExecutor { + fn default() -> Self { + Self::new() + } +} + impl SqlExecutor for EmbeddedSqlExecutor { fn exec(&self, _sql: &str, _params_json: &str) -> Result<ExecOutcome, SqlError> { Ok(ExecOutcome { diff --git a/crates/sql_core/src/executor_sqlite.rs b/crates/sql_core/src/executor_sqlite.rs @@ -40,7 +40,7 @@ impl SqlExecutor for SqliteExecutor { }); } let n = conn - .execute(sql, params_from_iter(binds.into_iter())) + .execute(sql, params_from_iter(binds)) .map_err(SqlError::from)?; let last_insert_id = conn.last_insert_rowid(); Ok(ExecOutcome { @@ -54,11 +54,8 @@ impl SqlExecutor for SqliteExecutor { let rows = { let conn = self.conn.lock().map_err(|_| SqlError::Internal)?; let mut stmt = conn.prepare(sql).map_err(SqlError::from)?; - let mapped = stmt.query_map(params_from_iter(binds.into_iter()), |row| { - sqlite_util::row_to_json(row) - })?; - let collected = mapped.collect::<Result<Vec<_>, _>>()?; - collected + let mapped = stmt.query_map(params_from_iter(binds), sqlite_util::row_to_json)?; + mapped.collect::<Result<Vec<_>, _>>()? }; Ok(Value::from(rows).to_string()) } diff --git a/crates/sql_core/src/utils.rs b/crates/sql_core/src/utils.rs @@ -106,10 +106,7 @@ pub fn build_select_query_with_meta<T: Serialize>( filter: Option<&T>, ) -> (String, Vec<Value>) { let (where_clause, binds) = match filter { - Some(f) => match build_where_clause_eq(f) { - Ok(t) => t, - Err(_) => (String::new(), Vec::new()), - }, + Some(f) => build_where_clause_eq(f).unwrap_or_default(), None => (String::new(), Vec::new()), }; let sql = format!("SELECT * FROM {table}{where_clause};"); diff --git a/crates/sql_wasm_bridge/src/lib.rs b/crates/sql_wasm_bridge/src/lib.rs @@ -88,15 +88,11 @@ pub fn rollback_tx() { let _ = js_exec(&format!("release savepoint {}", SAVEPOINT), "[]"); } -pub fn coverage_branch_probe(input: bool) -> &'static str { - if input { "bridge" } else { "bridge" } -} - #[cfg(test)] mod tests { use super::{ - begin_tx, commit_tx, coverage_branch_probe, exec, exec_calls, export_bytes, export_calls, - query, query_calls, rollback_tx, + begin_tx, commit_tx, exec, exec_calls, export_bytes, export_calls, query, query_calls, + rollback_tx, }; #[test] @@ -139,10 +135,4 @@ mod tests { .any(|(sql, _)| sql == "rollback to savepoint radroots_schema_tx") ); } - - #[test] - fn coverage_branch_probe_hits_both_paths() { - assert_eq!(coverage_branch_probe(true), "bridge"); - assert_eq!(coverage_branch_probe(false), "bridge"); - } } diff --git a/crates/trade/src/listing/codec.rs b/crates/trade/src/listing/codec.rs @@ -66,7 +66,7 @@ fn parse_u64_tag_value(value: Option<&String>, field: &str) -> Result<u64, Listi fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, ListingParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) .ok_or_else(|| ListingParseError::MissingTag(TAG_D.to_string()))?; let value = tag .get(1) @@ -205,7 +205,7 @@ fn listing_from_tags( let has_structured_location = tags .iter() - .any(|tag| tag.get(0).map(|k| k.as_str()) == Some(TAG_LOCATION) && tag.len() >= 3); + .any(|tag| tag.first().map(|k| k.as_str()) == Some(TAG_LOCATION) && tag.len() >= 3); for tag in tags { if tag.is_empty() { @@ -490,7 +490,7 @@ fn listing_from_tags( fn parse_farm_ref(tags: &[Vec<String>]) -> Result<RadrootsFarmRef, ListingParseError> { for tag in tags .iter() - .filter(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_A)) + .filter(|t| t.first().map(|s| s.as_str()) == Some(TAG_A)) { let value = tag .get(1) @@ -526,7 +526,7 @@ fn parse_farm_ref(tags: &[Vec<String>]) -> Result<RadrootsFarmRef, ListingParseE fn parse_farm_pubkey(tags: &[Vec<String>]) -> Result<String, ListingParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_P)) + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_P)) .ok_or_else(|| ListingParseError::MissingTag(TAG_P.to_string()))?; let value = tag .get(1) @@ -543,7 +543,7 @@ fn parse_resource_area( ) -> Result<Option<RadrootsResourceAreaRef>, ListingParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_RADROOTS_RESOURCE_AREA)); + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_RADROOTS_RESOURCE_AREA)); let Some(tag) = tag else { return Ok(None); }; @@ -585,7 +585,7 @@ fn parse_resource_area( fn parse_plot_ref(tags: &[Vec<String>]) -> Result<Option<RadrootsPlotRef>, ListingParseError> { let tag = tags .iter() - .find(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_RADROOTS_PLOT)); + .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_RADROOTS_PLOT)); let Some(tag) = tag else { return Ok(None); }; @@ -2339,18 +2339,18 @@ fn clean_value(value: &str) -> Option<String> { } fn set_if_empty(target: &mut String, value: Option<&String>) { - if target.trim().is_empty() { - if let Some(v) = value.and_then(|v| clean_value(v)) { - *target = v; - } + if target.trim().is_empty() + && let Some(v) = value.and_then(|v| clean_value(v)) + { + *target = v; } } fn set_optional(target: &mut Option<String>, value: Option<&String>) { - if target.is_none() { - if let Some(v) = value.and_then(|v| clean_value(v)) { - *target = Some(v); - } + if target.is_none() + && let Some(v) = value.and_then(|v| clean_value(v)) + { + *target = Some(v); } } diff --git a/crates/trade/src/listing/validation.rs b/crates/trade/src/listing/validation.rs @@ -125,7 +125,6 @@ pub fn validate_listing_event( let inventory_available = listing .inventory_available - .clone() .ok_or(TradeListingValidationError::MissingInventory)?; if inventory_available.is_sign_negative() { return Err(TradeListingValidationError::InvalidInventory); diff --git a/crates/trade/src/order.rs b/crates/trade/src/order.rs @@ -739,18 +739,83 @@ pub struct RadrootsListingInventoryAccountingProjection { pub issues: Vec<RadrootsListingInventoryAccountingIssue>, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsOrderReductionInputs<I, J, K, L, M, N, O, P, Q> { + pub requests: I, + pub decisions: J, + pub revision_proposals: K, + pub revision_decisions: L, + pub fulfillments: M, + pub cancellations: N, + pub receipts: O, + pub payments: P, + pub settlements: Q, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RadrootsGroupedOrderEventRecords { + pub requests: Vec<RadrootsOrderRequestRecord>, + pub decisions: Vec<RadrootsOrderDecisionRecord>, + pub revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, + pub revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, + pub fulfillments: Vec<RadrootsOrderFulfillmentRecord>, + pub cancellations: Vec<RadrootsOrderCancellationRecord>, + pub receipts: Vec<RadrootsOrderReceiptRecord>, + pub payments: Vec<RadrootsOrderPaymentEventRecord>, + pub settlements: Vec<RadrootsOrderSettlementRecord>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsListingInventoryAccountingInputs<I, J, K, L, M, N, O, P> { + pub bins: I, + pub requests: J, + pub decisions: K, + pub revision_proposals: L, + pub revision_decisions: M, + pub fulfillments: N, + pub cancellations: O, + pub receipts: P, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +struct RadrootsListingInventoryAccountingRecords { + bins: Vec<RadrootsListingInventoryBinAvailability>, + requests: Vec<RadrootsOrderRequestRecord>, + decisions: Vec<RadrootsOrderDecisionRecord>, + revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, + revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, + fulfillments: Vec<RadrootsOrderFulfillmentRecord>, + cancellations: Vec<RadrootsOrderCancellationRecord>, + receipts: Vec<RadrootsOrderReceiptRecord>, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +struct RadrootsOrderDecisionProjectionRecords { + revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, + revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, + fulfillments: Vec<RadrootsOrderFulfillmentRecord>, + cancellations: Vec<RadrootsOrderCancellationRecord>, + receipts: Vec<RadrootsOrderReceiptRecord>, + payments: Vec<RadrootsOrderPaymentEventRecord>, + settlements: Vec<RadrootsOrderSettlementRecord>, +} + +struct RadrootsReceiptProjectionInput<'a> { + order_id: &'a RadrootsOrderId, + request: &'a RadrootsOrderRequestRecord, + decision: &'a RadrootsOrderDecisionRecord, + agreement_event_id: &'a RadrootsEventId, + economics: &'a RadrootsOrderEconomics, + latest_fulfillment: Option<&'a RadrootsOrderFulfillmentRecord>, + fulfillments: &'a [RadrootsOrderFulfillmentRecord], + receipts: Vec<RadrootsOrderReceiptRecord>, + issues: &'a mut Vec<RadrootsOrderIssue>, +} + #[cfg_attr(coverage_nightly, coverage(off))] pub fn reduce_order_events<I, J, K, L, M, N, O, P, Q>( order_id: &RadrootsOrderId, - requests: I, - decisions: J, - revision_proposals: K, - revision_decisions: L, - fulfillments: M, - cancellations: N, - receipts: O, - payments: P, - settlements: Q, + inputs: RadrootsOrderReductionInputs<I, J, K, L, M, N, O, P, Q>, ) -> RadrootsOrderProjection where I: IntoIterator<Item = RadrootsOrderRequestRecord>, @@ -765,15 +830,17 @@ where { reduce_grouped_order_event_records( order_id, - requests.into_iter().collect(), - decisions.into_iter().collect(), - revision_proposals.into_iter().collect(), - revision_decisions.into_iter().collect(), - fulfillments.into_iter().collect(), - cancellations.into_iter().collect(), - receipts.into_iter().collect(), - payments.into_iter().collect(), - settlements.into_iter().collect(), + RadrootsGroupedOrderEventRecords { + requests: inputs.requests.into_iter().collect(), + decisions: inputs.decisions.into_iter().collect(), + revision_proposals: inputs.revision_proposals.into_iter().collect(), + revision_decisions: inputs.revision_decisions.into_iter().collect(), + fulfillments: inputs.fulfillments.into_iter().collect(), + cancellations: inputs.cancellations.into_iter().collect(), + receipts: inputs.receipts.into_iter().collect(), + payments: inputs.payments.into_iter().collect(), + settlements: inputs.settlements.into_iter().collect(), + }, ) } @@ -817,39 +884,33 @@ where reduce_grouped_order_event_records( order_id, - requests, - decisions, - revision_proposals, - revision_decisions, - fulfillments, - cancellations, - receipts, - payments, - settlements, + RadrootsGroupedOrderEventRecords { + requests, + decisions, + revision_proposals, + revision_decisions, + fulfillments, + cancellations, + receipts, + payments, + settlements, + }, ) } fn reduce_grouped_order_event_records( order_id: &RadrootsOrderId, - requests: Vec<RadrootsOrderRequestRecord>, - decisions: Vec<RadrootsOrderDecisionRecord>, - revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, - revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, - fulfillments: Vec<RadrootsOrderFulfillmentRecord>, - cancellations: Vec<RadrootsOrderCancellationRecord>, - receipts: Vec<RadrootsOrderReceiptRecord>, - payments: Vec<RadrootsOrderPaymentEventRecord>, - settlements: Vec<RadrootsOrderSettlementRecord>, + records: RadrootsGroupedOrderEventRecords, ) -> RadrootsOrderProjection { - let requests = unique_request_records(requests); - let decisions = unique_decision_records(decisions); - let revision_proposals = unique_revision_proposal_records(revision_proposals); - let revision_decisions = unique_revision_decision_records(revision_decisions); - let fulfillments = unique_fulfillment_records(fulfillments); - let cancellations = unique_cancellation_records(cancellations); - let receipts = unique_receipt_records(receipts); - let payments = unique_payment_records(payments); - let settlements = unique_settlement_records(settlements); + let requests = unique_request_records(records.requests); + let decisions = unique_decision_records(records.decisions); + let revision_proposals = unique_revision_proposal_records(records.revision_proposals); + let revision_decisions = unique_revision_decision_records(records.revision_decisions); + let fulfillments = unique_fulfillment_records(records.fulfillments); + let cancellations = unique_cancellation_records(records.cancellations); + let receipts = unique_receipt_records(records.receipts); + let payments = unique_payment_records(records.payments); + let settlements = unique_settlement_records(records.settlements); if requests.is_empty() && decisions.is_empty() && revision_proposals.is_empty() @@ -1016,13 +1077,15 @@ fn reduce_grouped_order_event_records( order_id, request, &valid_decisions[0], - valid_revision_proposals, - valid_revision_decisions, - fulfillments, - valid_cancellations, - valid_receipts, - payments, - settlements, + RadrootsOrderDecisionProjectionRecords { + revision_proposals: valid_revision_proposals, + revision_decisions: valid_revision_decisions, + fulfillments, + cancellations: valid_cancellations, + receipts: valid_receipts, + payments, + settlements, + }, ), _ => { let mut event_ids = valid_decisions @@ -1043,14 +1106,7 @@ fn reduce_grouped_order_event_records( pub fn reduce_listing_inventory_accounting<I, J, K, L, M, N, O, P>( listing_addr: &RadrootsListingAddress, listing_event_id: &RadrootsEventId, - bins: I, - requests: J, - decisions: K, - revision_proposals: L, - revision_decisions: M, - fulfillments: N, - cancellations: O, - receipts: P, + inputs: RadrootsListingInventoryAccountingInputs<I, J, K, L, M, N, O, P>, ) -> RadrootsListingInventoryAccountingProjection where I: IntoIterator<Item = RadrootsListingInventoryBinAvailability>, @@ -1065,55 +1121,50 @@ where reduce_listing_inventory_accounting_records( listing_addr, listing_event_id, - bins.into_iter().collect(), - requests.into_iter().collect(), - decisions.into_iter().collect(), - revision_proposals.into_iter().collect(), - revision_decisions.into_iter().collect(), - fulfillments.into_iter().collect(), - cancellations.into_iter().collect(), - receipts.into_iter().collect(), + RadrootsListingInventoryAccountingRecords { + bins: inputs.bins.into_iter().collect(), + requests: inputs.requests.into_iter().collect(), + decisions: inputs.decisions.into_iter().collect(), + revision_proposals: inputs.revision_proposals.into_iter().collect(), + revision_decisions: inputs.revision_decisions.into_iter().collect(), + fulfillments: inputs.fulfillments.into_iter().collect(), + cancellations: inputs.cancellations.into_iter().collect(), + receipts: inputs.receipts.into_iter().collect(), + }, ) } fn reduce_listing_inventory_accounting_records( listing_addr: &RadrootsListingAddress, listing_event_id: &RadrootsEventId, - bins: Vec<RadrootsListingInventoryBinAvailability>, - requests: Vec<RadrootsOrderRequestRecord>, - decisions: Vec<RadrootsOrderDecisionRecord>, - revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, - revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, - fulfillments: Vec<RadrootsOrderFulfillmentRecord>, - cancellations: Vec<RadrootsOrderCancellationRecord>, - receipts: Vec<RadrootsOrderReceiptRecord>, + records: RadrootsListingInventoryAccountingRecords, ) -> RadrootsListingInventoryAccountingProjection { - let (mut bins, mut issues) = normalized_listing_inventory_bins(bins); - let requests = unique_request_records(requests) + let (mut bins, mut issues) = normalized_listing_inventory_bins(records.bins); + let requests = unique_request_records(records.requests) .into_iter() .filter(|request| request.payload.listing_addr.as_str() == listing_addr.as_str()) .collect::<Vec<_>>(); - let decisions = unique_decision_records(decisions) + let decisions = unique_decision_records(records.decisions) .into_iter() .filter(|decision| decision.payload.listing_addr.as_str() == listing_addr.as_str()) .collect::<Vec<_>>(); - let revision_proposals = unique_revision_proposal_records(revision_proposals) + let revision_proposals = unique_revision_proposal_records(records.revision_proposals) .into_iter() .filter(|proposal| proposal.payload.listing_addr.as_str() == listing_addr.as_str()) .collect::<Vec<_>>(); - let revision_decisions = unique_revision_decision_records(revision_decisions) + let revision_decisions = unique_revision_decision_records(records.revision_decisions) .into_iter() .filter(|decision| decision.payload.listing_addr.as_str() == listing_addr.as_str()) .collect::<Vec<_>>(); - let fulfillments = unique_fulfillment_records(fulfillments) + let fulfillments = unique_fulfillment_records(records.fulfillments) .into_iter() .filter(|fulfillment| fulfillment.payload.listing_addr.as_str() == listing_addr.as_str()) .collect::<Vec<_>>(); - let cancellations = unique_cancellation_records(cancellations) + let cancellations = unique_cancellation_records(records.cancellations) .into_iter() .filter(|cancellation| cancellation.payload.listing_addr.as_str() == listing_addr.as_str()) .collect::<Vec<_>>(); - let receipts = unique_receipt_records(receipts) + let receipts = unique_receipt_records(records.receipts) .into_iter() .filter(|receipt| receipt.payload.listing_addr.as_str() == listing_addr.as_str()) .collect::<Vec<_>>(); @@ -1168,15 +1219,17 @@ fn reduce_listing_inventory_accounting_records( .collect::<Vec<_>>(); let projection = reduce_order_events( &order_id, - order_requests.clone(), - order_decisions.clone(), - order_revision_proposals.clone(), - order_revision_decisions.clone(), - order_fulfillments.clone(), - order_cancellations.clone(), - order_receipts.clone(), - Vec::<RadrootsOrderPaymentEventRecord>::new(), - Vec::<RadrootsOrderSettlementRecord>::new(), + RadrootsOrderReductionInputs { + requests: order_requests.clone(), + decisions: order_decisions.clone(), + revision_proposals: order_revision_proposals.clone(), + revision_decisions: order_revision_decisions.clone(), + fulfillments: order_fulfillments.clone(), + cancellations: order_cancellations.clone(), + receipts: order_receipts.clone(), + payments: Vec::<RadrootsOrderPaymentEventRecord>::new(), + settlements: Vec::<RadrootsOrderSettlementRecord>::new(), + }, ); match projection.status { RadrootsOrderStatus::Accepted @@ -3229,14 +3282,17 @@ fn decided_projection( order_id: &RadrootsOrderId, request: &RadrootsOrderRequestRecord, decision: &RadrootsOrderDecisionRecord, - revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, - revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, - fulfillments: Vec<RadrootsOrderFulfillmentRecord>, - cancellations: Vec<RadrootsOrderCancellationRecord>, - receipts: Vec<RadrootsOrderReceiptRecord>, - payments: Vec<RadrootsOrderPaymentEventRecord>, - settlements: Vec<RadrootsOrderSettlementRecord>, + records: RadrootsOrderDecisionProjectionRecords, ) -> RadrootsOrderProjection { + let RadrootsOrderDecisionProjectionRecords { + revision_proposals, + revision_decisions, + fulfillments, + cancellations, + receipts, + payments, + settlements, + } = records; let status = match &decision.payload.decision { RadrootsOrderDecisionOutcome::Accepted { .. } => RadrootsOrderStatus::Accepted, RadrootsOrderDecisionOutcome::Declined { .. } => RadrootsOrderStatus::Declined, @@ -3305,10 +3361,10 @@ fn decided_projection( } let decision_cancellations = cancellations .iter() - .cloned() .filter(|cancellation| { cancellation.prev_event_id == revision_state.lifecycle_parent_event_id }) + .cloned() .collect::<Vec<_>>(); for cancellation in cancellations.iter().filter(|cancellation| { cancellation.prev_event_id != revision_state.lifecycle_parent_event_id @@ -3391,17 +3447,17 @@ fn decided_projection( RadrootsOrderPaymentProjection::invalid(), ); } - let receipt_result = receipt_projection( + let receipt_result = receipt_projection(RadrootsReceiptProjectionInput { order_id, request, decision, - &revision_state.agreement_event_id, - &revision_state.economics, - latest.as_ref(), - &fulfillment_records, + agreement_event_id: &revision_state.agreement_event_id, + economics: &revision_state.economics, + latest_fulfillment: latest.as_ref(), + fulfillments: &fulfillment_records, receipts, - &mut issues, - ); + issues: &mut issues, + }); if let Some(mut projection) = receipt_result { projection.payment = payment; return projection; @@ -3500,16 +3556,19 @@ fn decided_projection( } fn receipt_projection( - order_id: &RadrootsOrderId, - request: &RadrootsOrderRequestRecord, - decision: &RadrootsOrderDecisionRecord, - agreement_event_id: &RadrootsEventId, - economics: &RadrootsOrderEconomics, - latest_fulfillment: Option<&RadrootsOrderFulfillmentRecord>, - fulfillments: &[RadrootsOrderFulfillmentRecord], - receipts: Vec<RadrootsOrderReceiptRecord>, - issues: &mut Vec<RadrootsOrderIssue>, + input: RadrootsReceiptProjectionInput<'_>, ) -> Option<RadrootsOrderProjection> { + let RadrootsReceiptProjectionInput { + order_id, + request, + decision, + agreement_event_id, + economics, + latest_fulfillment, + fulfillments, + receipts, + issues, + } = input; if receipts.is_empty() { return None; } @@ -3558,8 +3617,8 @@ fn receipt_projection( } let matching = receipts .iter() - .cloned() .filter(|receipt| receipt.prev_event_id == fulfillment.event_id) + .cloned() .collect::<Vec<_>>(); match single_lifecycle_child(&matching, |record| &record.event_id) { Ok(Some(receipt)) => Some(receipt_terminal_projection( @@ -3913,22 +3972,23 @@ mod tests { order_projection_for_order_id, }; use super::{ - RadrootsListingInventoryAccountingIssue, RadrootsListingInventoryAccountingProjection, - RadrootsListingInventoryBinAccounting, RadrootsListingInventoryBinAvailability, - RadrootsListingInventoryOrderReservation, RadrootsOrderCancellationRecord, - RadrootsOrderCanonicalizationError, RadrootsOrderDecisionRecord, - RadrootsOrderEventDecodeError, RadrootsOrderEventRecord, RadrootsOrderFulfillmentRecord, - RadrootsOrderIssue, RadrootsOrderPaymentEventRecord, RadrootsOrderPaymentProjection, - RadrootsOrderPaymentState, RadrootsOrderProjection, RadrootsOrderReceiptRecord, - RadrootsOrderRequestRecord, RadrootsOrderRevisionDecisionRecord, - RadrootsOrderRevisionProposalRecord, RadrootsOrderSettlementRecord, - RadrootsOrderSettlementState, RadrootsOrderStatus, add_inventory_reservation, - canonicalize_order_decision_for_signer, canonicalize_order_request_for_signer, - inventory_issue_event_ids, inventory_issue_id, inventory_issue_rank, - inventory_issue_sort_key, order_event_record_from_event, projection_issue_event_ids, - radroots_order_economics_digest, - reduce_listing_inventory_accounting as reduce_listing_inventory_accounting_with_revisions, - reduce_order_event_records, reduce_order_events as reduce_order_events_with_revisions, + RadrootsListingInventoryAccountingInputs, RadrootsListingInventoryAccountingIssue, + RadrootsListingInventoryAccountingProjection, RadrootsListingInventoryBinAccounting, + RadrootsListingInventoryBinAvailability, RadrootsListingInventoryOrderReservation, + RadrootsOrderCancellationRecord, RadrootsOrderCanonicalizationError, + RadrootsOrderDecisionRecord, RadrootsOrderEventDecodeError, RadrootsOrderEventRecord, + RadrootsOrderFulfillmentRecord, RadrootsOrderIssue, RadrootsOrderPaymentEventRecord, + RadrootsOrderPaymentProjection, RadrootsOrderPaymentState, RadrootsOrderProjection, + RadrootsOrderReceiptRecord, RadrootsOrderReductionInputs, RadrootsOrderRequestRecord, + RadrootsOrderRevisionDecisionRecord, RadrootsOrderRevisionProposalRecord, + RadrootsOrderSettlementRecord, RadrootsOrderSettlementState, RadrootsOrderStatus, + add_inventory_reservation, canonicalize_order_decision_for_signer, + canonicalize_order_request_for_signer, inventory_issue_event_ids, inventory_issue_id, + inventory_issue_rank, inventory_issue_sort_key, order_event_record_from_event, + projection_issue_event_ids, radroots_order_economics_digest, + reduce_listing_inventory_accounting as reduce_listing_inventory_accounting_with_revisions_inner, + reduce_order_event_records, + reduce_order_events as reduce_order_events_with_revisions_inner, }; const SELLER: &str = "1111111111111111111111111111111111111111111111111111111111111111"; @@ -5055,6 +5115,45 @@ mod tests { ) } + fn reduce_order_events_with_revisions<I, J, K, L, M, N, O, P, Q>( + order_id: &RadrootsOrderId, + requests: I, + decisions: J, + revision_proposals: K, + revision_decisions: L, + fulfillments: M, + cancellations: N, + receipts: O, + payments: P, + settlements: Q, + ) -> RadrootsOrderProjection + where + I: IntoIterator<Item = RadrootsOrderRequestRecord>, + J: IntoIterator<Item = RadrootsOrderDecisionRecord>, + K: IntoIterator<Item = RadrootsOrderRevisionProposalRecord>, + L: IntoIterator<Item = RadrootsOrderRevisionDecisionRecord>, + M: IntoIterator<Item = RadrootsOrderFulfillmentRecord>, + N: IntoIterator<Item = RadrootsOrderCancellationRecord>, + O: IntoIterator<Item = RadrootsOrderReceiptRecord>, + P: IntoIterator<Item = RadrootsOrderPaymentEventRecord>, + Q: IntoIterator<Item = RadrootsOrderSettlementRecord>, + { + reduce_order_events_with_revisions_inner( + order_id, + RadrootsOrderReductionInputs { + requests, + decisions, + revision_proposals, + revision_decisions, + fulfillments, + cancellations, + receipts, + payments, + settlements, + }, + ) + } + fn reduce_listing_inventory_accounting<I, J, K, L, M, N>( listing_addr: &str, listing_event_id: &str, @@ -5089,6 +5188,44 @@ mod tests { ) } + fn reduce_listing_inventory_accounting_with_revisions<I, J, K, L, M, N, O, P>( + listing_addr: &RadrootsListingAddress, + listing_event_id: &RadrootsEventId, + bins: I, + requests: J, + decisions: K, + revision_proposals: L, + revision_decisions: M, + fulfillments: N, + cancellations: O, + receipts: P, + ) -> RadrootsListingInventoryAccountingProjection + where + I: IntoIterator<Item = RadrootsListingInventoryBinAvailability>, + J: IntoIterator<Item = RadrootsOrderRequestRecord>, + K: IntoIterator<Item = RadrootsOrderDecisionRecord>, + L: IntoIterator<Item = RadrootsOrderRevisionProposalRecord>, + M: IntoIterator<Item = RadrootsOrderRevisionDecisionRecord>, + N: IntoIterator<Item = RadrootsOrderFulfillmentRecord>, + O: IntoIterator<Item = RadrootsOrderCancellationRecord>, + P: IntoIterator<Item = RadrootsOrderReceiptRecord>, + { + reduce_listing_inventory_accounting_with_revisions_inner( + listing_addr, + listing_event_id, + RadrootsListingInventoryAccountingInputs { + bins, + requests, + decisions, + revision_proposals, + revision_decisions, + fulfillments, + cancellations, + receipts, + }, + ) + } + #[test] fn canonicalize_order_request_sets_authority_and_trims_items() { let request = diff --git a/crates/trade/src/validation_receipt.rs b/crates/trade/src/validation_receipt.rs @@ -501,61 +501,61 @@ fn validate_expected_binding( receipt: &RadrootsTradeValidationReceipt, expected: RadrootsValidationReceiptExpectedBinding<'_>, ) -> Result<(), RadrootsValidationReceiptError> { - if let Some(order_id) = expected.order_id { - if tags.order_id != order_id { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "order_id", - )); - } + if let Some(order_id) = expected.order_id + && tags.order_id != order_id + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "order_id", + )); } - if let Some(listing_event_id) = expected.listing_event_id { - if tags.listing_event_id != listing_event_id { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "listing_event_id", - )); - } + if let Some(listing_event_id) = expected.listing_event_id + && tags.listing_event_id != listing_event_id + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "listing_event_id", + )); } - if let Some(event_set_root) = expected.event_set_root { - if tags.event_set_root != event_set_root { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "event_set_root", - )); - } + if let Some(event_set_root) = expected.event_set_root + && tags.event_set_root != event_set_root + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "event_set_root", + )); } - if let Some(reducer_output_root) = expected.reducer_output_root { - if tags.reducer_output_root != reducer_output_root { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "reducer_output_root", - )); - } + if let Some(reducer_output_root) = expected.reducer_output_root + && tags.reducer_output_root != reducer_output_root + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "reducer_output_root", + )); } - if let Some(public_values_hash) = expected.public_values_hash { - if tags.public_values_hash != public_values_hash { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "public_values_hash", - )); - } + if let Some(public_values_hash) = expected.public_values_hash + && tags.public_values_hash != public_values_hash + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "public_values_hash", + )); } - if let Some(proof_system) = expected.proof_system { - if tags.proof_system != proof_system { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "proof_system", - )); - } + if let Some(proof_system) = expected.proof_system + && tags.proof_system != proof_system + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "proof_system", + )); } - if let Some(program_hash) = expected.program_hash { - if receipt.proof.program_hash.as_deref() != Some(program_hash) { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "program_hash", - )); - } + if let Some(program_hash) = expected.program_hash + && receipt.proof.program_hash.as_deref() != Some(program_hash) + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "program_hash", + )); } - if let Some(verifying_key_hash) = expected.verifying_key_hash { - if receipt.proof.verifying_key_hash.as_deref() != Some(verifying_key_hash) { - return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( - "verifying_key_hash", - )); - } + if let Some(verifying_key_hash) = expected.verifying_key_hash + && receipt.proof.verifying_key_hash.as_deref() != Some(verifying_key_hash) + { + return Err(RadrootsValidationReceiptError::ExpectedBindingMismatch( + "verifying_key_hash", + )); } Ok(()) } diff --git a/crates/xtask/src/contract.rs b/crates/xtask/src/contract.rs @@ -2394,10 +2394,16 @@ fn should_synthesize_owner_contracts_for_tests(workspace_root: &Path) -> bool { .is_file() && workspace_root .join("crates") - .join("sdk") + .join("events_codec") .join("Cargo.toml") .is_file() && workspace_root + .join("crates") + .join("trade") + .join("Cargo.toml") + .is_file() + && workspace_root.join("spec").join("manifest.toml").is_file() + && workspace_root .join("policy") .join("coverage") .join("policy.toml") @@ -2858,13 +2864,13 @@ fn validate_operations_contract( mapping.language.id )); } - if let Some(module_format) = mapping.sdk.module_format.as_deref() { - if module_format.trim().is_empty() { - return Err(format!( - "sdk module_format must be non-empty for {}", - mapping.language.id - )); - } + if let Some(module_format) = mapping.sdk.module_format.as_deref() + && module_format.trim().is_empty() + { + return Err(format!( + "sdk module_format must be non-empty for {}", + mapping.language.id + )); } if mapping.operations.is_empty() { return Err(format!( @@ -3072,14 +3078,14 @@ fn validate_export_mappings(bundle: &ContractBundle) -> Result<(), String> { { return Err("artifacts fields must be non-empty for ts".to_string()); } - if let Some(expected_packages) = ts_packages.as_ref() { - if mapped_packages != *expected_packages { - return Err(format!( - "ts export packages {} must match manifest export.ts.packages {}", - join_set(&mapped_packages), - join_set(expected_packages) - )); - } + if let Some(expected_packages) = ts_packages.as_ref() + && mapped_packages != *expected_packages + { + return Err(format!( + "ts export packages {} must match manifest export.ts.packages {}", + join_set(&mapped_packages), + join_set(expected_packages) + )); } } } @@ -3115,13 +3121,13 @@ fn validate_export_mappings(bundle: &ContractBundle) -> Result<(), String> { mapping.language.id )); } - if let Some(module_format) = mapping.sdk.module_format.as_deref() { - if module_format.trim().is_empty() { - return Err(format!( - "sdk module_format must be non-empty for {}", - mapping.language.id - )); - } + if let Some(module_format) = mapping.sdk.module_format.as_deref() + && module_format.trim().is_empty() + { + return Err(format!( + "sdk module_format must be non-empty for {}", + mapping.language.id + )); } if mapping.operations.is_empty() { return Err(format!( @@ -3210,9 +3216,29 @@ fn parse_coverage_percent(raw: &str, field: &str, crate_name: &str) -> Result<f6 } } -fn load_coverage_refresh_rows( - workspace_root: &Path, -) -> Result<BTreeMap<String, (String, f64, f64, f64, f64)>, String> { +fn parse_branch_coverage_percent(raw: &str, crate_name: &str) -> Result<Option<f64>, String> { + if raw == "unavailable" { + return Ok(None); + } + parse_coverage_percent(raw, "branch", crate_name).map(Some) +} + +fn branch_coverage_fails(branch: Option<f64>, thresholds: CoverageThresholds) -> bool { + match branch { + Some(value) => value < thresholds.fail_under_branches, + None => thresholds.require_branches, + } +} + +fn branch_coverage_display(branch: Option<f64>) -> String { + branch + .map(|value| value.to_string()) + .unwrap_or_else(|| "unavailable".to_string()) +} + +type CoverageRefreshRows = BTreeMap<String, (String, f64, f64, Option<f64>, f64)>; + +fn load_coverage_refresh_rows(workspace_root: &Path) -> Result<CoverageRefreshRows, String> { let report_path = workspace_root .join("target") .join("coverage") @@ -3239,7 +3265,7 @@ fn load_coverage_refresh_rows( let status = parts[1].to_string(); let exec = parse_coverage_percent(parts[2], "exec", &crate_name)?; let func = parse_coverage_percent(parts[3], "func", &crate_name)?; - let branch = parse_coverage_percent(parts[4], "branch", &crate_name)?; + let branch = parse_branch_coverage_percent(parts[4], &crate_name)?; let region = parse_coverage_percent(parts[5], "region", &crate_name)?; if rows .insert(crate_name.clone(), (status, exec, func, branch, region)) @@ -3277,7 +3303,7 @@ fn validate_required_coverage_summary( } if *exec < thresholds.fail_under_exec_lines || *func < thresholds.fail_under_functions - || *branch < thresholds.fail_under_branches + || branch_coverage_fails(*branch, thresholds) || *region < thresholds.fail_under_regions { return Err(format!( @@ -3289,7 +3315,7 @@ fn validate_required_coverage_summary( thresholds.fail_under_regions, exec, func, - branch, + branch_coverage_display(*branch), region )); } @@ -3319,7 +3345,7 @@ fn validate_required_coverage_summary_with_policy( let thresholds = policy.thresholds_for_scope(crate_name); if *exec < thresholds.fail_under_exec_lines || *func < thresholds.fail_under_functions - || *branch < thresholds.fail_under_branches + || branch_coverage_fails(*branch, thresholds) || *region < thresholds.fail_under_regions { return Err(format!( @@ -3331,7 +3357,7 @@ fn validate_required_coverage_summary_with_policy( thresholds.fail_under_regions, exec, func, - branch, + branch_coverage_display(*branch), region )); } @@ -4836,6 +4862,16 @@ pub enum RadrootsCoreUnitDimension { fs::write( coverage_dir.join("coverage-refresh.tsv"), + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t100.0\t100.0\tunavailable\t100.0\tfile\n", + ) + .expect("write unavailable branch coverage file"); + let missing_branch_err = + validate_required_coverage_summary(&root, &required, strict_thresholds()) + .expect_err("branch coverage missing under strict policy"); + assert!(missing_branch_err.contains("unavailable")); + + fs::write( + coverage_dir.join("coverage-refresh.tsv"), "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t100.0\t100.0\t100.0\t99.9\tfile\n", ) .expect("write region coverage file"); @@ -4852,19 +4888,22 @@ pub enum RadrootsCoreUnitDimension { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); fs::write( coverage_dir.join("coverage-refresh.tsv"), - "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_events_codec\tpass\t100.0\t100.0\t100.0\t99.946385\tfile\n", + "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_events_codec\tpass\t100.0\t100.0\t100.0\t99.946385\tfile\nradroots_log\tpass\t100.0\t100.0\tunavailable\t100.0\tfile\n", ) .expect("write coverage file"); let policy_dir = root.join("policy").join("coverage"); fs::create_dir_all(&policy_dir).expect("create policy dir"); fs::write( policy_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[overrides.radroots_events_codec]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 99.946\nfail_under_branches = 100.0\ntemporary = true\nreason = \"publish 0.1.0-alpha.2 temporary coverage override\"\n\n[required]\ncrates = [\"radroots_events_codec\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[overrides.radroots_events_codec]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 99.946\nfail_under_branches = 100.0\ntemporary = true\nreason = \"publish 0.1.0-alpha.2 temporary coverage override\"\n\n[overrides.radroots_log]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = false\ntemporary = true\nreason = \"branch coverage is not applicable while the crate has no measured branch records\"\n\n[required]\ncrates = [\"radroots_events_codec\", \"radroots_log\"]\n", ) .expect("write coverage policy"); - let required = ["radroots_events_codec".to_string()] - .into_iter() - .collect::<BTreeSet<_>>(); + let required = [ + "radroots_events_codec".to_string(), + "radroots_log".to_string(), + ] + .into_iter() + .collect::<BTreeSet<_>>(); let policy = read_coverage_policy(&policy_dir.join("policy.toml")) .expect("parse override coverage policy"); validate_required_coverage_summary_with_policy(&root, &required, &policy) diff --git a/crates/xtask/src/coverage.rs b/crates/xtask/src/coverage.rs @@ -361,15 +361,15 @@ fn read_detailed_summary( if !variants.iter().any(|function| function.count > 0) { continue; } - if let Some(scope_filter) = scope_filter.as_deref() { - if !variants.iter().any(|function| { + if let Some(scope_filter) = scope_filter.as_deref() + && !variants.iter().any(|function| { function .filenames .iter() .any(|filename| filename.contains(scope_filter)) - }) { - continue; - } + }) + { + continue; } functions_total = functions_total.saturating_add(1); functions_covered = functions_covered.saturating_add(1); @@ -457,10 +457,9 @@ fn is_ignorable_synthetic_region( return true; } - let is_unexpected_panic_fallback = filename.ends_with("/tests.rs") + filename.ends_with("/tests.rs") && line.contains("panic!(\"unexpected") - && matches!(slice, Some("other") | Some("panic!")); - is_unexpected_panic_fallback + && matches!(slice, Some("other") | Some("panic!")) } impl CoveragePolicyFile { @@ -1550,7 +1549,7 @@ fn list_required_crates_with_root(root: &Path, writer: &mut dyn Write) -> Result } fn list_workspace_crates_with_root(root: &Path, writer: &mut dyn Write) -> Result<(), String> { - let crates = read_workspace_crates(&root)?; + let crates = read_workspace_crates(root)?; write_crate_names_output(writer, crates, "workspace crates") } @@ -1582,10 +1581,8 @@ fn run_with_root(args: &[String], root: &Path) -> Result<(), String> { Some(raw) => PathBuf::from(raw), None => PathBuf::from("target/coverage/coverage-refresh.tsv"), }; - let status_out_path = match parse_optional_string_arg(&args[1..], "status-out") { - Some(raw) => Some(PathBuf::from(raw)), - None => None, - }; + let status_out_path = + parse_optional_string_arg(&args[1..], "status-out").map(PathBuf::from); let required_crates = read_required_crates(&coverage_policy_path(root))?; let mut refresh_rows = @@ -1596,9 +1593,13 @@ fn run_with_root(args: &[String], root: &Path) -> Result<(), String> { let report_path = coverage_report_path(&reports_root, &crate_name); let report = read_gate_report(&report_path)?; let status = if report.result.pass { "pass" } else { "fail" }; - let branch = report.measured.branches_percent.unwrap_or(0.0); + let branch = report + .measured + .branches_percent + .map(|value| format!("{value:.6}")) + .unwrap_or_else(|| "unavailable".to_string()); refresh_rows.push_str(&format!( - "{}\t{}\t{:.6}\t{:.6}\t{:.6}\t{:.6}\t{}\n", + "{}\t{}\t{:.6}\t{:.6}\t{}\t{:.6}\t{}\n", crate_name, status, report.measured.executable_lines_percent, @@ -1610,23 +1611,21 @@ fn run_with_root(args: &[String], root: &Path) -> Result<(), String> { status_rows.push_str(&format!("{}\t{}\n", crate_name, status)); } - if let Some(parent) = out_path.parent() { - if !parent.as_os_str().is_empty() { - if let Err(err) = fs::create_dir_all(parent) { - return Err(format!("failed to create {}: {err}", parent.display())); - } - } + if let Some(parent) = out_path.parent() + && !parent.as_os_str().is_empty() + && let Err(err) = fs::create_dir_all(parent) + { + return Err(format!("failed to create {}: {err}", parent.display())); } fs::write(&out_path, refresh_rows) .map_err(|err| format!("failed to write {}: {err}", out_path.display()))?; if let Some(status_out_path) = status_out_path { - if let Some(parent) = status_out_path.parent() { - if !parent.as_os_str().is_empty() { - fs::create_dir_all(parent).map_err(|err| { - format!("failed to create {}: {err}", parent.display()) - })?; - } + if let Some(parent) = status_out_path.parent() + && !parent.as_os_str().is_empty() + { + fs::create_dir_all(parent) + .map_err(|err| format!("failed to create {}: {err}", parent.display()))?; } fs::write(&status_out_path, status_rows).map_err(|err| { format!("failed to write {}: {err}", status_out_path.display()) @@ -2306,7 +2305,7 @@ mod tests { fs::create_dir_all(&coverage_dir).expect("create coverage dir"); write_file( &coverage_dir.join("policy.toml"), - "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", + "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\", \"radroots_b\"]\n", ); let reports_root = root.join("target").join("coverage"); @@ -2348,6 +2347,44 @@ mod tests { } }"#, ); + let no_branch_crate_dir = reports_root.join("radroots_b"); + fs::create_dir_all(&no_branch_crate_dir).expect("create no branch crate dir"); + write_file( + &no_branch_crate_dir.join("gate-report.json"), + r#"{ + "scope": "radroots_b", + "thresholds": { + "executable_lines": 100.0, + "functions": 100.0, + "regions": 100.0, + "branches": 100.0, + "branches_required": false + }, + "measured": { + "executable_lines_percent": 100.0, + "executable_lines_source": "da", + "functions_percent": 100.0, + "branches_percent": null, + "branches_available": false, + "summary_lines_percent": 100.0, + "summary_regions_percent": 100.0 + }, + "counts": { + "executable_lines": { + "covered": 4, + "total": 4 + }, + "branches": { + "covered": 0, + "total": 0 + } + }, + "result": { + "pass": true, + "fail_reasons": [] + } +}"#, + ); let refresh_out = reports_root.join("coverage-refresh.tsv"); let status_out = reports_root.join("coverage-refresh-status.tsv"); @@ -2370,9 +2407,15 @@ mod tests { assert!( refresh.contains("radroots_a\tpass\t100.000000\t100.000000\t100.000000\t97.500000\t") ); + assert!( + refresh.contains("radroots_b\tpass\t100.000000\t100.000000\tunavailable\t100.000000\t") + ); let status = fs::read_to_string(&status_out).expect("read status summary"); - assert_eq!(status, "crate\tstatus\nradroots_a\tpass\n"); + assert_eq!( + status, + "crate\tstatus\nradroots_a\tpass\nradroots_b\tpass\n" + ); fs::remove_dir_all(root).expect("remove root"); diff --git a/crates/xtask/src/phase1_1.rs b/crates/xtask/src/phase1_1.rs @@ -153,8 +153,7 @@ fn is_raw_protocol_field(line: &str) -> bool { "pub bin_id: String,", "pub economics_digest: String,", ] - .iter() - .any(|field| line == *field) + .contains(&line) } fn is_allowed_raw_boundary(struct_name: &str) -> bool { diff --git a/policy/coverage/POLICY.md b/policy/coverage/POLICY.md @@ -9,7 +9,7 @@ The authoritative machine-readable contract is `policy/coverage/policy.toml`. - function coverage: 90.0 - region coverage: 90.0 - branch coverage: 90.0 -- branch records must be present in lcov data +- branch records must be present in lcov data unless a crate-specific policy override marks branch coverage as not applicable All four thresholds are release-blocking for required crates. This is the heavy-development coverage gate, not a 100% coverage requirement. @@ -32,6 +32,7 @@ Do not add low-value tests solely to chase crate-wide 100% coverage. - once required, the crate remains blocking on every canonical release-preflight run and any external automation that wraps that run - `coverage-refresh.tsv` must be generated from measured per-crate gate reports, not from synthetic pass rows - temporary overrides below 90/90/90/90 must stay explicit, scoped to a required crate, and tied to a release-preflight gap +- branch-record presence overrides are allowed only for crates whose coverage report has no measured branch records; when branch records exist, the active branch threshold remains binding ## required crate contract diff --git a/policy/coverage/policy.toml b/policy/coverage/policy.toml @@ -34,6 +34,15 @@ fail_under_branches = 81.264 temporary = true reason = "heavy-development branch coverage gap for publish 0.1.0-alpha.2" +[overrides.radroots_log] +fail_under_exec_lines = 90.0 +fail_under_functions = 90.0 +fail_under_regions = 90.0 +fail_under_branches = 90.0 +require_branches = false +temporary = true +reason = "branch coverage is not applicable while the crate has no measured branch records" + [overrides.radroots_nostr_signer] fail_under_exec_lines = 89.185 fail_under_functions = 90.0 @@ -42,6 +51,15 @@ fail_under_branches = 90.0 temporary = true reason = "heavy-development executable-line coverage gap for publish 0.1.0-alpha.2" +[overrides.radroots_replica_db_schema] +fail_under_exec_lines = 90.0 +fail_under_functions = 90.0 +fail_under_regions = 90.0 +fail_under_branches = 90.0 +require_branches = false +temporary = true +reason = "branch coverage is not applicable while the crate has no measured branch records" + [required] crates = [ "radroots_core", diff --git a/spec/RCLD.md b/spec/RCLD.md @@ -333,7 +333,7 @@ Purpose: parse and validate the canonical listing address used by trade flows Rust implementation sources: -- `crates/events_codec/src/order/decode.rs` +- `crates/events/src/ids.rs` Input: diff --git a/spec/README.md b/spec/README.md @@ -184,8 +184,10 @@ Coverage governance is defined under `policy/coverage/`: - per-crate profiles: `policy/coverage/profiles.toml` Required Rust crates are gated at `90/90/90/90` (exec lines, functions, -branches, regions), with branch records required. This is not a 100% coverage -target. Temporary crate-specific overrides below 90% must remain explicit in +branches, regions), with branch records required unless a crate-specific policy +override marks branch coverage as not applicable because no branch records are +measured. This is not a 100% coverage target. Temporary crate-specific +overrides below 90% or branch-record presence overrides must remain explicit in the machine-readable policy. ## Release Policy diff --git a/spec/conformance/vectors/order/build_order_decision_draft.v1.json b/spec/conformance/vectors/order/build_order_decision_draft.v1.json @@ -6,13 +6,13 @@ "id": "order_build_order_decision_draft_accept_minimal_001", "kind": "order.build_order_decision_draft", "input": { - "root_event_id": "order-request-event", - "prev_event_id": "order-request-event", + "root_event_id": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "prev_event_id": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "payload": { "order_id": "order-1", - "listing_addr": "30402:seller_pubkey:AAAAAAAAAAAAAAAAAAAAAg", - "buyer_pubkey": "buyer_pubkey", - "seller_pubkey": "seller_pubkey", + "listing_addr": "30402:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:AAAAAAAAAAAAAAAAAAAAAg", + "buyer_pubkey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "seller_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "decision": { "decision": "accepted", "inventory_commitments": [ diff --git a/spec/conformance/vectors/order/build_order_request_draft.v1.json b/spec/conformance/vectors/order/build_order_request_draft.v1.json @@ -7,14 +7,14 @@ "kind": "order.build_order_request_draft", "input": { "listing_event": { - "id": "listing-snapshot", + "id": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "relays": "wss://relay.example.com" }, "payload": { "order_id": "order-1", - "listing_addr": "30402:seller_pubkey:AAAAAAAAAAAAAAAAAAAAAg", - "buyer_pubkey": "buyer_pubkey", - "seller_pubkey": "seller_pubkey", + "listing_addr": "30402:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:AAAAAAAAAAAAAAAAAAAAAg", + "buyer_pubkey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "seller_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "items": [ { "bin_id": "bin-1", diff --git a/spec/conformance/vectors/order/parse_listing_address.v1.json b/spec/conformance/vectors/order/parse_listing_address.v1.json @@ -6,7 +6,7 @@ "id": "order_parse_listing_address_minimal_001", "kind": "order.parse_listing_address", "input": { - "listing_addr": "30402:seller_pubkey:AAAAAAAAAAAAAAAAAAAAAg" + "listing_addr": "30402:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:AAAAAAAAAAAAAAAAAAAAAg" }, "expected": { "address_shape": "trade_listing_address" diff --git a/spec/conformance/vectors/order/parse_order_decision.v1.json b/spec/conformance/vectors/order/parse_order_decision.v1.json @@ -8,7 +8,7 @@ "input": { "event": { "kind": 3423, - "author": "seller_pubkey", + "author": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "content_type": "TradeOrderDecision", "tags_shape": "order_order_decision_tags" } diff --git a/spec/conformance/vectors/order/parse_order_request.v1.json b/spec/conformance/vectors/order/parse_order_request.v1.json @@ -8,7 +8,7 @@ "input": { "event": { "kind": 3422, - "author": "buyer_pubkey", + "author": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "content_type": "TradeOrderRequested", "tags_shape": "order_order_request_tags", "payload_shape": "order_order_request_with_economics" diff --git a/spec/operations.toml b/spec/operations.toml @@ -375,10 +375,8 @@ signing = "native" transport = "native" [operations.order_parse_listing_address.implementation] -rust_modules = ["crates/events_codec/src/order/decode.rs"] -rust_types = [ - "radroots_events::ids::RadrootsListingAddress", -] +rust_modules = ["crates/events/src/ids.rs"] +rust_types = ["radroots_events::ids::RadrootsListingAddress"] [operations.order_parse_listing_address.conformance] vector = "spec/conformance/vectors/order/parse_listing_address.v1.json"