error.rs (7181B)
1 //! Stable Nostr transport failures. 2 3 use core::fmt; 4 5 /// Error returned by the Nostr transport adapter. 6 #[derive(Clone, Debug, Eq, PartialEq)] 7 #[non_exhaustive] 8 pub enum Error { 9 /// No relay was configured. 10 EmptyRelaySet, 11 /// The configured relay count exceeds the adapter bound. 12 TooManyRelays { max: usize, actual: usize }, 13 /// A canonical relay URL occurs more than once. 14 DuplicateRelayUrl, 15 /// The URL is not a valid canonical Nostr relay target. 16 InvalidRelayUrl, 17 /// The URL scheme is not permitted by the selected policy. 18 RelaySchemeDenied, 19 /// The URL destination is not permitted by the selected policy. 20 RelayDestinationDenied, 21 /// DNS resolution produced no addresses. 22 EmptyResolution, 23 /// A resolved address violates the selected policy. 24 ResolvedAddressDenied, 25 /// An endpoint policy is incompatible with its host profile kind. 26 RelayProfilePolicyMismatch, 27 /// A connection or request timeout is outside its governed bounds. 28 InvalidTimeout { field: &'static str, value_ms: u64 }, 29 /// The per-operation connection limit is outside its governed bounds. 30 InvalidConnectionLimit { value: usize }, 31 /// The reconnect delay policy is empty, inverted, or exceeds its bound. 32 InvalidReconnectBackoff { 33 initial_delay_ms: u64, 34 max_delay_ms: u64, 35 }, 36 /// A transport-neutral target is not a Nostr target. 37 UnexpectedTransport, 38 /// A relay cursor contains a noncanonical event position. 39 InvalidRelayCursor, 40 /// The generic transport target rejected the relay URL. 41 Target, 42 /// The relay challenge is empty, malformed, or outside its time bounds. 43 InvalidAuthChallenge, 44 /// A different live challenge already exists for this relay. 45 AuthChallengeConflict, 46 /// No matching live challenge exists for this relay. 47 AuthChallengeMissing, 48 /// The matching challenge expired before response submission. 49 AuthChallengeExpired, 50 /// The host supplied no signed NIP-42 response. 51 AuthSignerUnavailable, 52 /// The signed response does not match the pending relay and challenge. 53 AuthResponseMismatch, 54 /// The signed response is malformed or cryptographically invalid. 55 AuthResponseInvalid, 56 /// The host explicitly rejected the pending challenge. 57 AuthRejected, 58 /// Internal authentication state could not be accessed safely. 59 AuthStateUnavailable, 60 /// The relay rejected or failed the explicit AUTH message. 61 AuthTransport, 62 } 63 64 impl fmt::Display for Error { 65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 66 match self { 67 Self::EmptyRelaySet => formatter.write_str("relay set must not be empty"), 68 Self::TooManyRelays { max, actual } => { 69 write!(formatter, "relay count {actual} exceeds maximum {max}") 70 } 71 Self::DuplicateRelayUrl => formatter.write_str("duplicate relay URL"), 72 Self::InvalidRelayUrl => formatter.write_str("invalid relay URL"), 73 Self::RelaySchemeDenied => formatter.write_str("relay URL scheme is denied by policy"), 74 Self::RelayDestinationDenied => { 75 formatter.write_str("relay URL destination is denied by policy") 76 } 77 Self::EmptyResolution => formatter.write_str("relay URL resolved to no addresses"), 78 Self::ResolvedAddressDenied => { 79 formatter.write_str("relay URL resolved to a denied address") 80 } 81 Self::RelayProfilePolicyMismatch => { 82 formatter.write_str("relay endpoint policy does not match its profile kind") 83 } 84 Self::InvalidTimeout { field, value_ms } => { 85 write!(formatter, "invalid {field} timeout: {value_ms}ms") 86 } 87 Self::InvalidConnectionLimit { value } => { 88 write!(formatter, "invalid connection limit: {value}") 89 } 90 Self::InvalidReconnectBackoff { 91 initial_delay_ms, 92 max_delay_ms, 93 } => write!( 94 formatter, 95 "invalid reconnect backoff: initial={initial_delay_ms}ms max={max_delay_ms}ms" 96 ), 97 Self::UnexpectedTransport => formatter.write_str("expected Nostr transport target"), 98 Self::InvalidRelayCursor => formatter.write_str("invalid relay cursor"), 99 Self::Target => formatter.write_str("transport target error"), 100 Self::InvalidAuthChallenge => formatter.write_str("invalid NIP-42 challenge"), 101 Self::AuthChallengeConflict => { 102 formatter.write_str("a different NIP-42 challenge is already pending") 103 } 104 Self::AuthChallengeMissing => { 105 formatter.write_str("matching NIP-42 challenge is not pending") 106 } 107 Self::AuthChallengeExpired => formatter.write_str("NIP-42 challenge expired"), 108 Self::AuthSignerUnavailable => { 109 formatter.write_str("NIP-42 response signer is unavailable") 110 } 111 Self::AuthResponseMismatch => { 112 formatter.write_str("NIP-42 response does not match its challenge") 113 } 114 Self::AuthResponseInvalid => formatter.write_str("NIP-42 response is invalid"), 115 Self::AuthRejected => formatter.write_str("NIP-42 challenge was rejected"), 116 Self::AuthStateUnavailable => { 117 formatter.write_str("NIP-42 authentication state is unavailable") 118 } 119 Self::AuthTransport => formatter.write_str("NIP-42 relay submission failed"), 120 } 121 } 122 } 123 124 impl std::error::Error for Error {} 125 126 #[cfg(test)] 127 mod tests { 128 use super::*; 129 use std::error::Error as _; 130 131 #[test] 132 fn every_error_has_a_stable_nonempty_message() { 133 let errors = [ 134 Error::EmptyRelaySet, 135 Error::TooManyRelays { max: 1, actual: 2 }, 136 Error::DuplicateRelayUrl, 137 Error::InvalidRelayUrl, 138 Error::RelaySchemeDenied, 139 Error::RelayDestinationDenied, 140 Error::EmptyResolution, 141 Error::ResolvedAddressDenied, 142 Error::RelayProfilePolicyMismatch, 143 Error::InvalidTimeout { 144 field: "request", 145 value_ms: 0, 146 }, 147 Error::InvalidConnectionLimit { value: 0 }, 148 Error::InvalidReconnectBackoff { 149 initial_delay_ms: 0, 150 max_delay_ms: 1, 151 }, 152 Error::UnexpectedTransport, 153 Error::InvalidRelayCursor, 154 Error::Target, 155 Error::InvalidAuthChallenge, 156 Error::AuthChallengeConflict, 157 Error::AuthChallengeMissing, 158 Error::AuthChallengeExpired, 159 Error::AuthSignerUnavailable, 160 Error::AuthResponseMismatch, 161 Error::AuthResponseInvalid, 162 Error::AuthRejected, 163 Error::AuthStateUnavailable, 164 Error::AuthTransport, 165 ]; 166 for error in errors { 167 assert!(!error.to_string().is_empty()); 168 assert!(error.source().is_none()); 169 } 170 } 171 }