myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit 3e873414506d509799cc36d1a6c2722e4fb533cc
parent 40e68ec92c6f421ed5158d76deb59dea58b374af
Author: triesap <tyson@radroots.org>
Date:   Wed, 15 Jul 2026 22:45:59 +0000

build: make myc validation warning-clean

- derive defaults and tighten internal CLI output enum storage

- simplify runtime, persistence, discovery, policy, and transport control flow

- clean integration-test relay and metadata helpers for clippy

- validate with myc fmt, clippy, and full workspace tests

Diffstat:
Msrc/app/runtime.rs | 17+++++++----------
Msrc/cli.rs | 26++++++++++++++++----------
Msrc/config.rs | 47+++++++++++++++--------------------------------
Msrc/custody.rs | 28++++++++++++++--------------
Msrc/discovery.rs | 58+++++++++++++++++++++++++++++++---------------------------
Msrc/paths.rs | 9++-------
Msrc/persistence.rs | 36++++++++++++++++++------------------
Msrc/policy.rs | 84+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/transport.rs | 29+++++++++++++++--------------
Msrc/transport/nip46.rs | 4++--
Mtests/discovery_cli.rs | 39+++++++++++++++++++++------------------
Mtests/nip46_e2e.rs | 47++++++++++++++++++++++++-----------------------
12 files changed, 213 insertions(+), 211 deletions(-)

diff --git a/src/app/runtime.rs b/src/app/runtime.rs @@ -536,9 +536,8 @@ impl MycRuntime { return Err(self.wrap_recovery_error( &record, MycError::InvalidOperation(format!( - "signer publish workflow `{}` is in `{}` instead of `published_pending_finalize` during startup recovery", - workflow.workflow_id, - format!("{:?}", workflow.state) + "signer publish workflow `{}` is in `{:?}` instead of `published_pending_finalize` during startup recovery", + workflow.workflow_id, workflow.state )), )); } @@ -695,15 +694,14 @@ impl MycRuntime { return Err(self.wrap_recovery_error( record, MycError::InvalidOperation(format!( - "delivery outbox job `{}` expects signer workflow kind `{}` but found `{}`", - record.job_id, - format!("{kind_label:?}"), - format!("{:?}", workflow.kind), + "delivery outbox job `{}` expects signer workflow kind `{kind_label:?}` but found `{:?}`", + record.job_id, workflow.kind )), )); } - if let Some(connection_id) = record.connection_id.as_ref() { - if &workflow.connection_id != connection_id { + if let Some(connection_id) = record.connection_id.as_ref() + && &workflow.connection_id != connection_id + { return Err(self.wrap_recovery_error( record, MycError::InvalidOperation(format!( @@ -711,7 +709,6 @@ impl MycRuntime { record.job_id, workflow.connection_id )), )); - } } Ok(workflow) }); diff --git a/src/cli.rs b/src/cli.rs @@ -368,16 +368,16 @@ pub struct MycDiscoveryRepairAttemptSummaryOutput { #[derive(Debug, Serialize, PartialEq, Eq)] #[serde(untagged)] pub enum MycDiscoveryRepairAttemptOutput { - Summary(MycDiscoveryRepairAttemptSummaryOutput), + Summary(Box<MycDiscoveryRepairAttemptSummaryOutput>), Records(MycDiscoveryRepairAttemptRecordsOutput), } #[derive(Debug, Serialize, PartialEq, Eq)] #[serde(untagged)] pub enum MycStatusOutput { - Signer(MycStatusSignerOutput), - Summary(MycStatusSummaryOutput), - Full(MycStatusFullOutput), + Signer(Box<MycStatusSignerOutput>), + Summary(Box<MycStatusSummaryOutput>), + Full(Box<MycStatusFullOutput>), } pub async fn run_from_env() -> Result<(), MycError> { @@ -392,11 +392,15 @@ pub async fn run_from_env() -> Result<(), MycError> { MycCommand::Status { view } => { let runtime = MycRuntime::bootstrap(config)?; let output = match view { - MycStatusView::Signer => MycStatusOutput::Signer(collect_status_signer(&runtime)?), + MycStatusView::Signer => { + MycStatusOutput::Signer(Box::new(collect_status_signer(&runtime)?)) + } MycStatusView::Summary => { - MycStatusOutput::Summary(collect_status_summary(&runtime).await?) + MycStatusOutput::Summary(Box::new(collect_status_summary(&runtime).await?)) + } + MycStatusView::Full => { + MycStatusOutput::Full(Box::new(collect_status_full(&runtime).await?)) } - MycStatusView::Full => MycStatusOutput::Full(collect_status_full(&runtime).await?), }; print_json(&output) } @@ -896,9 +900,11 @@ fn load_discovery_repair_attempt_output( } match view { - MycDiscoveryRepairAttemptView::Summary => Ok(MycDiscoveryRepairAttemptOutput::Summary( - MycDiscoveryRepairAttemptSummaryOutput::from_records(attempt_id, &records)?, - )), + MycDiscoveryRepairAttemptView::Summary => { + Ok(MycDiscoveryRepairAttemptOutput::Summary(Box::new( + MycDiscoveryRepairAttemptSummaryOutput::from_records(attempt_id, &records)?, + ))) + } MycDiscoveryRepairAttemptView::Records => Ok(MycDiscoveryRepairAttemptOutput::Records( MycDiscoveryRepairAttemptRecordsOutput { attempt_id: attempt_id.to_owned(), diff --git a/src/config.rs b/src/config.rs @@ -90,7 +90,7 @@ pub struct MycDiscoveryConfig { pub metadata: MycDiscoveryMetadataConfig, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct MycDiscoveryMetadataConfig { pub name: Option<String>, @@ -121,9 +121,10 @@ pub enum MycConnectionApproval { Deny, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MycIdentityBackend { + #[default] EncryptedFile, HostVault, ManagedAccount, @@ -301,18 +302,6 @@ impl Default for MycDiscoveryConfig { } } -impl Default for MycDiscoveryMetadataConfig { - fn default() -> Self { - Self { - name: None, - display_name: None, - about: None, - website: None, - picture: None, - } - } -} - impl Default for MycPolicyConfig { fn default() -> Self { Self { @@ -333,12 +322,6 @@ impl Default for MycPolicyConfig { } } -impl Default for MycIdentityBackend { - fn default() -> Self { - Self::EncryptedFile - } -} - impl MycConnectionApproval { pub fn into_signer_approval_requirement(self) -> RadrootsNostrSignerApprovalRequirement { match self { @@ -873,12 +856,12 @@ impl MycConfig { } })?; - if let Some(output_dir) = self.logging.output_dir.as_ref() { - if output_dir.as_os_str().is_empty() { - return Err(MycError::InvalidConfig( - "logging.output_dir must not be empty when set".to_owned(), - )); - } + if let Some(output_dir) = self.logging.output_dir.as_ref() + && output_dir.as_os_str().is_empty() + { + return Err(MycError::InvalidConfig( + "logging.output_dir must not be empty when set".to_owned(), + )); } if self.paths.state_dir.as_os_str().is_empty() { @@ -1911,12 +1894,12 @@ impl MycDiscoveryConfig { validate_nostrconnect_url_template(template)?; } - if let Some(path) = self.nip05_output_path.as_ref() { - if path.as_os_str().is_empty() { - return Err(MycError::InvalidConfig( - "discovery.nip05_output_path must not be empty".to_owned(), - )); - } + if let Some(path) = self.nip05_output_path.as_ref() + && path.as_os_str().is_empty() + { + return Err(MycError::InvalidConfig( + "discovery.nip05_output_path must not be empty".to_owned(), + )); } if self.resolved_public_relays(transport)?.is_empty() { diff --git a/src/custody.rs b/src/custody.rs @@ -250,7 +250,7 @@ enum MycExternalCommandExecuteError { trait MycExternalCommandExecutor: Send + Sync { fn execute( &self, - command_path: &PathBuf, + command_path: &Path, request_json: &[u8], timeout: Duration, ) -> Result<MycExternalCommandOutput, MycExternalCommandExecuteError>; @@ -262,7 +262,7 @@ struct MycProcessCommandExecutor; impl MycExternalCommandExecutor for MycProcessCommandExecutor { fn execute( &self, - command_path: &PathBuf, + command_path: &Path, request_json: &[u8], timeout: Duration, ) -> Result<MycExternalCommandOutput, MycExternalCommandExecuteError> { @@ -1137,7 +1137,7 @@ impl MycIdentityProvider { fn load_external_command_identity( &self, - command_path: &PathBuf, + command_path: &Path, timeout: Duration, executor: &dyn MycExternalCommandExecutor, ) -> Result<(RadrootsIdentityPublic, RadrootsNostrPublicKey), MycError> { @@ -1153,13 +1153,13 @@ impl MycIdentityProvider { .map_err(|error| match error { MycExternalCommandExecuteError::Io(source) => MycError::CustodyExternalCommandIo { role: self.role.clone(), - path: command_path.clone(), + path: command_path.to_path_buf(), source, }, MycExternalCommandExecuteError::TimedOut => { MycError::CustodyExternalCommandTimedOut { role: self.role.clone(), - path: command_path.clone(), + path: command_path.to_path_buf(), timeout_secs: timeout.as_secs(), } } @@ -1168,7 +1168,7 @@ impl MycIdentityProvider { let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned(); return Err(MycError::CustodyExternalCommandFailed { role: self.role.clone(), - path: command_path.clone(), + path: command_path.to_path_buf(), status: output .status .map(|status| status.to_string()) @@ -1184,14 +1184,14 @@ impl MycIdentityProvider { serde_json::from_slice(&output.stdout).map_err(|source| { MycError::CustodyExternalCommandParse { role: self.role.clone(), - path: command_path.clone(), + path: command_path.to_path_buf(), source, } })?; if let Some(error) = response.error { return Err(MycError::CustodyExternalCommandFailed { role: self.role.clone(), - path: command_path.clone(), + path: command_path.to_path_buf(), status: "0".to_owned(), stderr: error, }); @@ -1201,7 +1201,7 @@ impl MycIdentityProvider { .identity .ok_or_else(|| MycError::CustodyExternalCommandInvalidIdentity { role: self.role.clone(), - path: command_path.clone(), + path: command_path.to_path_buf(), message: "missing `identity` in describe response".to_owned(), })?; validate_external_command_public_identity(&self.role, command_path, identity) @@ -1623,14 +1623,14 @@ fn load_identity_from_nip49_file( fn validate_external_command_public_identity( role: &str, - command_path: &PathBuf, + command_path: &Path, identity: RadrootsIdentityPublic, ) -> Result<(RadrootsIdentityPublic, RadrootsNostrPublicKey), MycError> { let public_key = RadrootsNostrPublicKey::parse(identity.public_key_hex.as_str()).map_err(|error| { MycError::CustodyExternalCommandInvalidIdentity { role: role.to_owned(), - path: command_path.clone(), + path: command_path.to_path_buf(), message: format!( "invalid public_key_hex `{}`: {error}", identity.public_key_hex @@ -1641,7 +1641,7 @@ fn validate_external_command_public_identity( if identity.id != expected_id { return Err(MycError::CustodyExternalCommandInvalidIdentity { role: role.to_owned(), - path: command_path.clone(), + path: command_path.to_path_buf(), message: format!( "identity id `{}` does not match public_key_hex `{}`", identity.id, identity.public_key_hex @@ -1739,7 +1739,7 @@ mod tests { impl MycExternalCommandExecutor for FakeExternalCommandExecutor { fn execute( &self, - _command_path: &PathBuf, + _command_path: &Path, request_json: &[u8], _timeout: Duration, ) -> Result<MycExternalCommandOutput, MycExternalCommandExecuteError> { @@ -2206,7 +2206,7 @@ mod tests { impl MycExternalCommandExecutor for TimeoutExternalCommandExecutor { fn execute( &self, - _command_path: &PathBuf, + _command_path: &Path, _request_json: &[u8], _timeout: Duration, ) -> Result<MycExternalCommandOutput, MycExternalCommandExecuteError> { diff --git a/src/discovery.rs b/src/discovery.rs @@ -381,13 +381,13 @@ impl MycDiscoveryContext { output_path: impl AsRef<Path>, ) -> Result<MycRenderedNip05Output, MycError> { let output_path = output_path.as_ref().to_path_buf(); - if let Some(parent) = output_path.parent() { - if !parent.as_os_str().is_empty() { - fs::create_dir_all(parent).map_err(|source| MycError::DiscoveryIo { - path: parent.to_path_buf(), - source, - })?; - } + if let Some(parent) = output_path.parent() + && !parent.as_os_str().is_empty() + { + fs::create_dir_all(parent).map_err(|source| MycError::DiscoveryIo { + path: parent.to_path_buf(), + source, + })?; } let json = self.render_nip05_json_pretty()?; fs::write(&output_path, json).map_err(|source| MycError::DiscoveryIo { @@ -835,7 +835,7 @@ pub async fn refresh_nip89( .with_attempt_id(attempt_id.clone()) .with_planned_repair_relays(refresh_relay_urls.clone()), ); - return Ok(MycRefreshedNip89Output { + Ok(MycRefreshedNip89Output { attempt_id, status, force, @@ -847,7 +847,7 @@ pub async fn refresh_nip89( repair_results, remaining_repair_relays, published: Some(published), - }); + }) } Err(error) => { let repair_results = @@ -878,7 +878,7 @@ pub async fn refresh_nip89( .with_attempt_id(attempt_id.clone()) .with_planned_repair_relays(refresh_relay_urls.clone()), ); - return Err(error.with_discovery_refresh_attempt_id(attempt_id)); + Err(error.with_discovery_refresh_attempt_id(attempt_id)) } } } @@ -1366,7 +1366,7 @@ async fn fetch_live_nip89_state( let fetched_relay = fetched_relay.ok_or_else(|| { MycError::InvalidOperation("missing discovery relay fetch result".to_owned()) })?; - all_events.extend(fetched_relay.relay_events.into_iter()); + all_events.extend(fetched_relay.relay_events); relay_states.push(fetched_relay.relay_state); } @@ -1809,12 +1809,14 @@ fn latest_live_event_id(live_groups: &[MycLiveNip89Group]) -> Option<&str> { } fn build_metadata(config: &MycDiscoveryMetadataConfig) -> Option<RadrootsNostrMetadata> { - let mut metadata = RadrootsNostrMetadata::default(); - metadata.name = sanitize_optional_string(config.name.as_deref()); - metadata.display_name = sanitize_optional_string(config.display_name.as_deref()); - metadata.about = sanitize_optional_string(config.about.as_deref()); - metadata.website = sanitize_optional_string(config.website.as_deref()); - metadata.picture = sanitize_optional_string(config.picture.as_deref()); + let metadata = RadrootsNostrMetadata { + name: sanitize_optional_string(config.name.as_deref()), + display_name: sanitize_optional_string(config.display_name.as_deref()), + about: sanitize_optional_string(config.about.as_deref()), + website: sanitize_optional_string(config.website.as_deref()), + picture: sanitize_optional_string(config.picture.as_deref()), + ..RadrootsNostrMetadata::default() + }; if metadata.name.is_none() && metadata.display_name.is_none() && metadata.about.is_none() @@ -1866,13 +1868,13 @@ fn write_pretty_json<T>(path: &Path, value: &T) -> Result<(), MycError> where T: Serialize, { - if let Some(parent) = path.parent() { - if !parent.as_os_str().is_empty() { - fs::create_dir_all(parent).map_err(|source| MycError::DiscoveryIo { - path: parent.to_path_buf(), - source, - })?; - } + if let Some(parent) = path.parent() + && !parent.as_os_str().is_empty() + { + fs::create_dir_all(parent).map_err(|source| MycError::DiscoveryIo { + path: parent.to_path_buf(), + source, + })?; } let encoded = serde_json::to_string_pretty(value)?; fs::write(path, encoded).map_err(|source| MycError::DiscoveryIo { @@ -2163,9 +2165,11 @@ mod tests { #[test] fn build_metadata_ignores_blank_fields() { - let mut metadata = crate::config::MycDiscoveryMetadataConfig::default(); - metadata.name = Some(" ".to_owned()); - metadata.about = Some(" ready ".to_owned()); + let metadata = crate::config::MycDiscoveryMetadataConfig { + name: Some(" ".to_owned()), + about: Some(" ready ".to_owned()), + ..crate::config::MycDiscoveryMetadataConfig::default() + }; let built = build_metadata(&metadata).expect("metadata"); diff --git a/src/paths.rs b/src/paths.rs @@ -47,9 +47,10 @@ pub struct MycPathsConfig { pub user_identity_profile_path: Option<PathBuf>, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MycPathProfile { + #[default] InteractiveUser, ServiceHost, RepoLocal, @@ -91,12 +92,6 @@ impl Default for MycPathsConfig { } } -impl Default for MycPathProfile { - fn default() -> Self { - Self::InteractiveUser - } -} - impl MycPathProfile { pub fn as_str(self) -> &'static str { match self { diff --git a/src/persistence.rs b/src/persistence.rs @@ -478,15 +478,15 @@ pub fn verify_restored_state( let signer_state = load_existing_signer_state(config, &signer_state_path)?; let configured_signer_identity = signer_identity.to_public(); - if let Some(existing_signer_identity) = signer_state.signer_identity.as_ref() { - if existing_signer_identity.id != configured_signer_identity.id { - return Err(MycError::SignerIdentityMismatch { - identity_path: config.paths.signer_identity_path.clone(), - state_path: signer_state_path.clone(), - configured_identity_id: configured_signer_identity.id.to_string(), - persisted_identity_id: existing_signer_identity.id.to_string(), - }); - } + if let Some(existing_signer_identity) = signer_state.signer_identity.as_ref() + && existing_signer_identity.id != configured_signer_identity.id + { + return Err(MycError::SignerIdentityMismatch { + identity_path: config.paths.signer_identity_path.clone(), + state_path: signer_state_path.clone(), + configured_identity_id: configured_signer_identity.id.to_string(), + persisted_identity_id: existing_signer_identity.id.to_string(), + }); } let runtime_audit_record_count = load_existing_runtime_audit_record_count(config, &audit_dir)?; @@ -565,14 +565,14 @@ fn import_signer_state_json_to_sqlite( Duration::from_secs(config.custody.external_command_timeout_secs), )?; let configured_signer_identity = signer_identity_provider.load_identity()?.to_public(); - if let Some(imported_signer_identity) = source_state.signer_identity.as_ref() { - if imported_signer_identity.id != configured_signer_identity.id { - return Err(MycError::SignerIdentityImportMismatch { - state_path: source_path.clone(), - configured_identity_id: configured_signer_identity.id.to_string(), - imported_identity_id: imported_signer_identity.id.to_string(), - }); - } + if let Some(imported_signer_identity) = source_state.signer_identity.as_ref() + && imported_signer_identity.id != configured_signer_identity.id + { + return Err(MycError::SignerIdentityImportMismatch { + state_path: source_path.clone(), + configured_identity_id: configured_signer_identity.id.to_string(), + imported_identity_id: imported_signer_identity.id.to_string(), + }); } let destination_store = RadrootsNostrSqliteSignerStore::open(&destination_path)?; @@ -727,7 +727,7 @@ fn restore_identity_reference( MycPersistenceIdentityReferenceField::EncryptedKeyPath => current_source .path .as_ref() - .map(|path| encrypted_identity_wrapping_key_path(path)), + .map(encrypted_identity_wrapping_key_path), MycPersistenceIdentityReferenceField::ProfilePath => { current_source.profile_path.clone() } diff --git a/src/policy.rs b/src/policy.rs @@ -205,12 +205,11 @@ impl MycPolicyContext { "auth challenge expired; require a new auth challenge".to_owned(), )); } - } else if self.should_require_fresh_auth(connection, &request_message.request) { - if let Some(reason) = + } else if self.should_require_fresh_auth(connection, &request_message.request) + && let Some(reason) = self.require_auth_challenge_with_guardrails(backend, connection)? - { - return Ok(Some(reason)); - } + { + return Ok(Some(reason)); } Ok(None) @@ -756,12 +755,16 @@ mod tests { #[test] fn connect_decision_prefers_deny_then_trust_then_default() { - let mut config = MycPolicyConfig::default(); - config.connection_approval = MycConnectionApproval::ExplicitUser; - config.trusted_client_pubkeys = - vec!["2222222222222222222222222222222222222222222222222222222222222222".to_owned()]; - config.denied_client_pubkeys = - vec!["3333333333333333333333333333333333333333333333333333333333333333".to_owned()]; + let config = MycPolicyConfig { + connection_approval: MycConnectionApproval::ExplicitUser, + trusted_client_pubkeys: vec![ + "2222222222222222222222222222222222222222222222222222222222222222".to_owned(), + ], + denied_client_pubkeys: vec![ + "3333333333333333333333333333333333333333333333333333333333333333".to_owned(), + ], + ..MycPolicyConfig::default() + }; let policy = MycPolicyContext::from_config(&config).expect("policy"); assert_eq!( @@ -786,16 +789,18 @@ mod tests { #[test] fn auto_granted_permissions_apply_policy_ceiling_and_kind_limits() { - let mut config = MycPolicyConfig::default(); - config.permission_ceiling = vec![ - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), - RadrootsNostrConnectPermission::with_parameter( - RadrootsNostrConnectMethod::SignEvent, - "kind:1", - ), - ] - .into(); - config.allowed_sign_event_kinds = vec![1]; + let config = MycPolicyConfig { + permission_ceiling: vec![ + RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), + RadrootsNostrConnectPermission::with_parameter( + RadrootsNostrConnectMethod::SignEvent, + "kind:1", + ), + ] + .into(), + allowed_sign_event_kinds: vec![1], + ..MycPolicyConfig::default() + }; let policy = MycPolicyContext::from_config(&config).expect("policy"); let requested_permissions: RadrootsNostrConnectPermissions = vec![ @@ -814,8 +819,10 @@ mod tests { #[test] fn request_denied_reason_applies_sign_event_kind_limits() { - let mut config = MycPolicyConfig::default(); - config.allowed_sign_event_kinds = vec![1]; + let config = MycPolicyConfig { + allowed_sign_event_kinds: vec![1], + ..MycPolicyConfig::default() + }; let policy = MycPolicyContext::from_config(&config).expect("policy"); let manager = in_memory_manager(); let backend = backend_for(&manager); @@ -843,11 +850,12 @@ mod tests { #[test] fn validate_operator_grants_rejects_out_of_policy_permissions() { - let mut config = MycPolicyConfig::default(); - config.permission_ceiling = - RadrootsNostrConnectPermissions::from(vec![RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::Nip04Encrypt, - )]); + let config = MycPolicyConfig { + permission_ceiling: RadrootsNostrConnectPermissions::from(vec![ + RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), + ]), + ..MycPolicyConfig::default() + }; let policy = MycPolicyContext::from_config(&config).expect("policy"); let error = policy @@ -869,10 +877,12 @@ mod tests { fn prepare_request_requires_fresh_auth_after_authorized_ttl() { let client_public_key = public_key("2222222222222222222222222222222222222222222222222222222222222222"); - let mut config = MycPolicyConfig::default(); - config.trusted_client_pubkeys = vec![client_public_key.to_hex()]; - config.auth_url = Some("https://auth.example".to_owned()); - config.auth_authorized_ttl_secs = Some(1); + let config = MycPolicyConfig { + trusted_client_pubkeys: vec![client_public_key.to_hex()], + auth_url: Some("https://auth.example".to_owned()), + auth_authorized_ttl_secs: Some(1), + ..MycPolicyConfig::default() + }; let policy = MycPolicyContext::from_config(&config).expect("policy"); let manager = in_memory_manager(); let backend = backend_for(&manager); @@ -923,10 +933,12 @@ mod tests { fn prepare_request_requires_fresh_auth_after_inactivity() { let client_public_key = public_key("2323232323232323232323232323232323232323232323232323232323232323"); - let mut config = MycPolicyConfig::default(); - config.trusted_client_pubkeys = vec![client_public_key.to_hex()]; - config.auth_url = Some("https://auth.example".to_owned()); - config.reauth_after_inactivity_secs = Some(1); + let config = MycPolicyConfig { + trusted_client_pubkeys: vec![client_public_key.to_hex()], + auth_url: Some("https://auth.example".to_owned()), + reauth_after_inactivity_secs: Some(1), + ..MycPolicyConfig::default() + }; let policy = MycPolicyContext::from_config(&config).expect("policy"); let manager = in_memory_manager(); let backend = backend_for(&manager); diff --git a/src/transport.rs b/src/transport.rs @@ -544,18 +544,19 @@ mod tests { #[test] fn bootstrap_builds_transport_snapshot_when_enabled() { - let mut config = MycTransportConfig::default(); - config.enabled = true; - config.connect_timeout_secs = 15; - config.relays = vec![ - "wss://relay.example.com".to_owned(), - "wss://relay2.example.com".to_owned(), - ]; - config.delivery_policy = MycTransportDeliveryPolicy::Quorum; - config.delivery_quorum = Some(2); - config.publish_max_attempts = 3; - config.publish_initial_backoff_millis = 125; - config.publish_max_backoff_millis = 500; + let config = MycTransportConfig { + enabled: true, + connect_timeout_secs: 15, + relays: vec![ + "wss://relay.example.com".to_owned(), + "wss://relay2.example.com".to_owned(), + ], + delivery_policy: MycTransportDeliveryPolicy::Quorum, + delivery_quorum: Some(2), + publish_max_attempts: 3, + publish_initial_backoff_millis: 125, + publish_max_backoff_millis: 500, + }; let transport = MycNostrTransport::bootstrap(&config, &signer_identity()) .expect("transport") @@ -695,13 +696,13 @@ mod tests { ) -> RadrootsNostrOutput<RadrootsNostrEventId> { let success = succeeded_relays .iter() - .map(|relay| RadrootsNostrRelayUrl::parse(*relay).expect("success relay")) + .map(|relay| RadrootsNostrRelayUrl::parse(relay).expect("success relay")) .collect::<HashSet<_>>(); let failed = failed_relays .iter() .map(|(relay, error)| { ( - RadrootsNostrRelayUrl::parse(*relay).expect("failed relay"), + RadrootsNostrRelayUrl::parse(relay).expect("failed relay"), (*error).to_owned(), ) }) diff --git a/src/transport/nip46.rs b/src/transport/nip46.rs @@ -118,7 +118,7 @@ impl RadrootsNostrSignerNip46Signer for MycNip46Signer { ) -> Result<String, radroots_nostr_signer::prelude::RadrootsNostrSignerError> { self.signer .user_identity() - .nip04_decrypt(public_key, ciphertext.to_owned()) + .nip04_decrypt(public_key, ciphertext) .map_err(|error| { radroots_nostr_signer::prelude::RadrootsNostrSignerError::Sign(error.to_string()) }) @@ -144,7 +144,7 @@ impl RadrootsNostrSignerNip46Signer for MycNip46Signer { ) -> Result<String, radroots_nostr_signer::prelude::RadrootsNostrSignerError> { self.signer .user_identity() - .nip44_decrypt(public_key, ciphertext.to_owned()) + .nip44_decrypt(public_key, ciphertext) .map_err(|error| { radroots_nostr_signer::prelude::RadrootsNostrSignerError::Sign(error.to_string()) }) diff --git a/tests/discovery_cli.rs b/tests/discovery_cli.rs @@ -285,15 +285,12 @@ async fn accept_published_event( .filters .iter() .any(|filter| filter.match_event(&event, MatchEventOptions::new())) + && let Some(sender) = state.senders.get(&subscription.connection_id).cloned() { - if let Some(sender) = state.senders.get(&subscription.connection_id).cloned() { - let message = RelayMessage::event( - subscription.subscription_id.clone(), - event.clone(), - ) - .as_json(); - subscriber_messages.push((sender, Message::Text(message.into()))); - } + let message = + RelayMessage::event(subscription.subscription_id.clone(), event.clone()) + .as_json(); + subscriber_messages.push((sender, Message::Text(message.into()))); } } } @@ -637,8 +634,10 @@ async fn conflicted_refresh_requires_force_through_the_cli() -> TestResult<()> { let mut second_spec = RadrootsNostrApplicationHandlerSpec::new(vec![24_133]); second_spec.identifier = Some("myc".to_owned()); second_spec.relays = vec!["wss://relay-b.example.com".to_owned()]; - let mut metadata = RadrootsNostrMetadata::default(); - metadata.name = Some("conflict".to_owned()); + let metadata = RadrootsNostrMetadata { + name: Some("conflict".to_owned()), + ..RadrootsNostrMetadata::default() + }; second_spec.metadata = Some(metadata); publish_handler_event(relay.url(), &app_identity, &second_spec).await?; @@ -1181,20 +1180,24 @@ async fn discovery_diff_surfaces_relay_provenance_through_the_cli() -> TestResul matched_spec.nostrconnect_url = Some(format!( "https://signer.example.com/connect?uri={encoded_bunker_uri}" )); - let mut matched_metadata = RadrootsNostrMetadata::default(); - matched_metadata.name = Some("myc".to_owned()); - matched_metadata.display_name = Some("Mycorrhiza".to_owned()); - matched_metadata.about = Some("NIP-46 signer".to_owned()); - matched_metadata.website = Some("https://signer.example.com".to_owned()); - matched_metadata.picture = Some("https://signer.example.com/logo.png".to_owned()); + let matched_metadata = RadrootsNostrMetadata { + name: Some("myc".to_owned()), + display_name: Some("Mycorrhiza".to_owned()), + about: Some("NIP-46 signer".to_owned()), + website: Some("https://signer.example.com".to_owned()), + picture: Some("https://signer.example.com/logo.png".to_owned()), + ..RadrootsNostrMetadata::default() + }; matched_spec.metadata = Some(matched_metadata); publish_handler_event(relay_a.url(), &app_identity, &matched_spec).await?; let mut drifted_spec = RadrootsNostrApplicationHandlerSpec::new(vec![24_133]); drifted_spec.identifier = Some("myc".to_owned()); drifted_spec.relays = vec!["wss://stale.example.com".to_owned()]; - let mut drifted_metadata = RadrootsNostrMetadata::default(); - drifted_metadata.name = Some("stale".to_owned()); + let drifted_metadata = RadrootsNostrMetadata { + name: Some("stale".to_owned()), + ..RadrootsNostrMetadata::default() + }; drifted_spec.metadata = Some(drifted_metadata); publish_handler_event(relay_b.url(), &app_identity, &drifted_spec).await?; diff --git a/tests/nip46_e2e.rs b/tests/nip46_e2e.rs @@ -375,15 +375,12 @@ async fn accept_published_event( .filters .iter() .any(|filter| filter.match_event(&event, MatchEventOptions::new())) + && let Some(sender) = state.senders.get(&subscription.connection_id).cloned() { - if let Some(sender) = state.senders.get(&subscription.connection_id).cloned() { - let message = RelayMessage::event( - subscription.subscription_id.clone(), - event.clone(), - ) - .as_json(); - subscriber_messages.push((sender, Message::Text(message.into()))); - } + let message = + RelayMessage::event(subscription.subscription_id.clone(), event.clone()) + .as_json(); + subscriber_messages.push((sender, Message::Text(message.into()))); } } notify.notify_waiters(); @@ -1060,7 +1057,7 @@ async fn external_nostr_client_compatibility_covers_signed_and_crypto_methods() let nip04_reply_ciphertext = nostr::nips::nip04::encrypt( peer_identity.keys().secret_key(), &user_public_key, - "reply via nip04".to_owned(), + "reply via nip04", )?; let (_, nip04_decrypt_response) = publish_external_request_and_wait_for_response( &relay, @@ -1108,7 +1105,7 @@ async fn external_nostr_client_compatibility_covers_signed_and_crypto_methods() let nip44_reply_ciphertext = nip44::encrypt( peer_identity.keys().secret_key(), &user_public_key, - "reply via nip44".to_owned(), + "reply via nip44", Version::V2, )?; let (_, nip44_decrypt_response) = publish_external_request_and_wait_for_response( @@ -1276,7 +1273,7 @@ async fn external_nostr_client_ignores_unrelated_signer_events_before_response() relay.wait_for_subscription_count(1).await?; - let noise_event = build_signer_noise_event(&signer_identity, base_created_at); + let noise_event = build_signer_noise_event(signer_identity, base_created_at); publish_event(relay.url(), &noise_event).await?; let (_, ping_response) = publish_external_request_and_wait_for_response( @@ -1368,7 +1365,7 @@ async fn live_listener_consumes_connect_secret_only_after_successful_publish() - .contains("blocked by test relay") ); let outbox_records = wait_for_delivery_outbox_records(&runtime, |records| { - records.len() >= 1 && records[0].status == MycDeliveryOutboxStatus::Failed + !records.is_empty() && records[0].status == MycDeliveryOutboxStatus::Failed }) .await?; assert_eq!( @@ -2337,7 +2334,7 @@ async fn connect_accept_retries_without_consuming_secret_until_publish_succeeds( .contains("blocked by test relay") ); let outbox_records = wait_for_delivery_outbox_records(&runtime, |records| { - records.len() >= 1 && records[0].status == MycDeliveryOutboxStatus::Failed + !records.is_empty() && records[0].status == MycDeliveryOutboxStatus::Failed }) .await?; assert_eq!( @@ -2589,7 +2586,7 @@ async fn connect_accept_rejects_when_quorum_delivery_policy_is_not_met() -> Test ); assert_eq!(operation_audit[0].publish_attempt_count, Some(1)); let outbox_records = wait_for_delivery_outbox_records(&runtime, |records| { - records.len() >= 1 && records[0].status == MycDeliveryOutboxStatus::Failed + !records.is_empty() && records[0].status == MycDeliveryOutboxStatus::Failed }) .await?; assert_eq!( @@ -2790,7 +2787,7 @@ async fn auth_replay_restores_pending_request_until_publish_succeeds() -> TestRe .contains("preserved pending auth challenge") ); let outbox_records = wait_for_delivery_outbox_records(&runtime, |records| { - records.len() >= 1 && records[0].status == MycDeliveryOutboxStatus::Failed + !records.is_empty() && records[0].status == MycDeliveryOutboxStatus::Failed }) .await?; assert_eq!( @@ -2938,7 +2935,7 @@ async fn explicit_nip89_publish_uses_app_identity_and_records_audit() -> TestRes .contains("1/1 relays acknowledged publish") ); let outbox_records = wait_for_delivery_outbox_records(&runtime, |records| { - records.len() >= 1 && records[0].status == MycDeliveryOutboxStatus::Finalized + !records.is_empty() && records[0].status == MycDeliveryOutboxStatus::Finalized }) .await?; assert_eq!( @@ -3147,7 +3144,7 @@ async fn explicit_nip89_publish_retries_cleanly_after_rejection() -> TestResult< .contains("blocked by test relay") ); let outbox_records = wait_for_delivery_outbox_records(&runtime, |records| { - records.len() >= 1 && records[0].status == MycDeliveryOutboxStatus::Failed + !records.is_empty() && records[0].status == MycDeliveryOutboxStatus::Failed }) .await?; assert_eq!( @@ -3274,7 +3271,7 @@ async fn fetch_live_nip89_parallelizes_relay_fetch_and_preserves_configured_orde slow_c.url(), slow_d.url(), ]; - let mut expected_relay_states = vec![ + let mut expected_relay_states = [ ( slow_a.url().to_owned(), MycDiscoveryRelayFetchStatus::Unavailable, @@ -3447,7 +3444,7 @@ async fn refresh_nip89_publishes_when_live_handler_is_missing() -> TestResult<() .as_ref() .expect("published discovery output"); let outbox_records = wait_for_delivery_outbox_records(&runtime, |records| { - records.len() >= 1 && records[0].status == MycDeliveryOutboxStatus::Finalized + !records.is_empty() && records[0].status == MycDeliveryOutboxStatus::Finalized }) .await?; assert_eq!( @@ -3637,8 +3634,10 @@ async fn refresh_nip89_republishes_when_live_handler_drifted() -> TestResult<()> drifted_spec.relays = vec!["wss://wrong.example.com".to_owned()]; drifted_spec.nostrconnect_url = Some("https://wrong.example.com/connect?uri=nostrconnect%3A%2F%2Fstale".to_owned()); - let mut metadata = RadrootsNostrMetadata::default(); - metadata.name = Some("stale".to_owned()); + let metadata = RadrootsNostrMetadata { + name: Some("stale".to_owned()), + ..RadrootsNostrMetadata::default() + }; drifted_spec.metadata = Some(metadata); publish_handler_event(relay.url(), &app_identity, &drifted_spec).await?; relay @@ -3962,8 +3961,10 @@ async fn diff_live_nip89_surfaces_relay_divergence_with_provenance() -> TestResu let mut drifted_spec = RadrootsNostrApplicationHandlerSpec::new(vec![24_133]); drifted_spec.identifier = Some("myc".to_owned()); drifted_spec.relays = vec!["wss://stale.example.com".to_owned()]; - let mut drifted_metadata = RadrootsNostrMetadata::default(); - drifted_metadata.name = Some("stale".to_owned()); + let drifted_metadata = RadrootsNostrMetadata { + name: Some("stale".to_owned()), + ..RadrootsNostrMetadata::default() + }; drifted_spec.metadata = Some(drifted_metadata); publish_handler_event(relay_b.url(), &app_identity, &drifted_spec).await?;