app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

error.rs (7705B)


      1 use std::error::Error;
      2 use std::fmt::{self, Debug, Display, Formatter};
      3 
      4 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
      5 pub enum SafeErrorCode {
      6     InvalidPublicKey,
      7     InvalidSecretKey,
      8     InvalidIdentityMetadata,
      9     InvalidProfileMetadata,
     10     InvalidApplicationState,
     11     IdentityAlreadyExists,
     12     IdentityNotFound,
     13     KeyringUnavailable,
     14     CredentialMissing,
     15     StorageUnavailable,
     16     StorageCorrupt,
     17     StorageQuarantined,
     18     StorageBackupInvalid,
     19     UnsupportedSchemaVersion,
     20     RepairUnauthorized,
     21     PendingOperationRecoveryRequired,
     22     InvalidRelayConfiguration,
     23     RelayConnectionFailed,
     24     ProfileRefreshFailed,
     25     ObserverRegistrationFailed,
     26     NativeLibraryLoadFailed,
     27     AvailabilityInvalidInput,
     28     AvailabilityUnsupportedProfile,
     29     AvailabilityScopeMismatch,
     30     AvailabilityStaleQuery,
     31     AvailabilityCapacity,
     32     AvailabilityUnavailable,
     33 }
     34 
     35 /// Finite availability failures with fixed public diagnostic messages.
     36 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     37 pub enum AvailabilityFailure {
     38     InvalidInput,
     39     UnsupportedProfile,
     40     ScopeMismatch,
     41     StaleQuery,
     42     Capacity,
     43     Unavailable,
     44 }
     45 
     46 #[derive(Clone, Copy, Eq, PartialEq)]
     47 pub struct SafeMessage(&'static str);
     48 
     49 impl SafeMessage {
     50     #[must_use]
     51     pub const fn new(message: &'static str) -> Self {
     52         Self(message)
     53     }
     54 
     55     #[must_use]
     56     pub const fn as_str(self) -> &'static str {
     57         self.0
     58     }
     59 }
     60 
     61 impl Debug for SafeMessage {
     62     fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     63         formatter.debug_tuple("SafeMessage").field(&self.0).finish()
     64     }
     65 }
     66 
     67 impl Display for SafeMessage {
     68     fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     69         formatter.write_str(self.0)
     70     }
     71 }
     72 
     73 #[derive(Clone, Copy, Eq, PartialEq)]
     74 pub struct SafeError {
     75     code: SafeErrorCode,
     76     message: SafeMessage,
     77 }
     78 
     79 impl SafeError {
     80     #[must_use]
     81     pub const fn new(code: SafeErrorCode, message: SafeMessage) -> Self {
     82         Self { code, message }
     83     }
     84 
     85     #[must_use]
     86     pub const fn code(self) -> SafeErrorCode {
     87         self.code
     88     }
     89 
     90     #[must_use]
     91     pub const fn message(self) -> SafeMessage {
     92         self.message
     93     }
     94 }
     95 
     96 impl Debug for SafeError {
     97     fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     98         formatter
     99             .debug_struct("SafeError")
    100             .field("code", &self.code)
    101             .field("message", &self.message)
    102             .finish()
    103     }
    104 }
    105 
    106 impl Display for SafeError {
    107     fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    108         Display::fmt(&self.message, formatter)
    109     }
    110 }
    111 
    112 impl Error for SafeError {}
    113 
    114 impl From<AvailabilityFailure> for SafeError {
    115     fn from(failure: AvailabilityFailure) -> Self {
    116         let (code, message) = match failure {
    117             AvailabilityFailure::InvalidInput => (
    118                 SafeErrorCode::AvailabilityInvalidInput,
    119                 "The availability request is invalid.",
    120             ),
    121             AvailabilityFailure::UnsupportedProfile => (
    122                 SafeErrorCode::AvailabilityUnsupportedProfile,
    123                 "This availability profile is unsupported.",
    124             ),
    125             AvailabilityFailure::ScopeMismatch => (
    126                 SafeErrorCode::AvailabilityScopeMismatch,
    127                 "The availability request belongs to another scope.",
    128             ),
    129             AvailabilityFailure::StaleQuery => (
    130                 SafeErrorCode::AvailabilityStaleQuery,
    131                 "The availability query is stale.",
    132             ),
    133             AvailabilityFailure::Capacity => (
    134                 SafeErrorCode::AvailabilityCapacity,
    135                 "The availability operation reached its capacity.",
    136             ),
    137             AvailabilityFailure::Unavailable => (
    138                 SafeErrorCode::AvailabilityUnavailable,
    139                 "Availability discovery is unavailable.",
    140             ),
    141         };
    142         Self::new(code, SafeMessage::new(message))
    143     }
    144 }
    145 
    146 impl From<crate::AvailabilityQueryError> for SafeError {
    147     fn from(error: crate::AvailabilityQueryError) -> Self {
    148         use crate::AvailabilityQueryError;
    149 
    150         let failure = match error {
    151             AvailabilityQueryError::InvalidInput | AvailabilityQueryError::InputTooLarge => {
    152                 AvailabilityFailure::InvalidInput
    153             }
    154             AvailabilityQueryError::ScopeMismatch => AvailabilityFailure::ScopeMismatch,
    155             AvailabilityQueryError::StaleQuery => AvailabilityFailure::StaleQuery,
    156             AvailabilityQueryError::Capacity => AvailabilityFailure::Capacity,
    157         };
    158         failure.into()
    159     }
    160 }
    161 
    162 #[cfg(test)]
    163 mod tests {
    164     use super::{SafeError, SafeErrorCode, SafeMessage};
    165 
    166     #[test]
    167     fn safe_error_formats_only_a_static_public_message() {
    168         let error = SafeError::new(
    169             SafeErrorCode::InvalidSecretKey,
    170             SafeMessage::new("The secret key is invalid."),
    171         );
    172 
    173         assert_eq!(error.to_string(), "The secret key is invalid.");
    174         assert_eq!(error.code(), SafeErrorCode::InvalidSecretKey);
    175         assert_eq!(error.message().as_str(), "The secret key is invalid.");
    176         assert!(!format!("{error:?}").contains("nsec1unsafe-test-value"));
    177     }
    178 
    179     #[test]
    180     fn availability_failures_and_query_errors_are_static() {
    181         use super::AvailabilityFailure;
    182         use crate::AvailabilityQueryError;
    183 
    184         for (failure, code, message) in [
    185             (
    186                 AvailabilityFailure::InvalidInput,
    187                 SafeErrorCode::AvailabilityInvalidInput,
    188                 "The availability request is invalid.",
    189             ),
    190             (
    191                 AvailabilityFailure::UnsupportedProfile,
    192                 SafeErrorCode::AvailabilityUnsupportedProfile,
    193                 "This availability profile is unsupported.",
    194             ),
    195             (
    196                 AvailabilityFailure::ScopeMismatch,
    197                 SafeErrorCode::AvailabilityScopeMismatch,
    198                 "The availability request belongs to another scope.",
    199             ),
    200             (
    201                 AvailabilityFailure::StaleQuery,
    202                 SafeErrorCode::AvailabilityStaleQuery,
    203                 "The availability query is stale.",
    204             ),
    205             (
    206                 AvailabilityFailure::Capacity,
    207                 SafeErrorCode::AvailabilityCapacity,
    208                 "The availability operation reached its capacity.",
    209             ),
    210             (
    211                 AvailabilityFailure::Unavailable,
    212                 SafeErrorCode::AvailabilityUnavailable,
    213                 "Availability discovery is unavailable.",
    214             ),
    215         ] {
    216             let error = SafeError::from(failure);
    217             assert_eq!(error.code(), code);
    218             assert_eq!(error.message().as_str(), message);
    219             assert_eq!(error.to_string(), message);
    220             assert!(!format!("{error:?}").contains("HCAV_PRIVATE_REMOTE_PAYLOAD"));
    221         }
    222         for (query, code) in [
    223             (
    224                 AvailabilityQueryError::InvalidInput,
    225                 SafeErrorCode::AvailabilityInvalidInput,
    226             ),
    227             (
    228                 AvailabilityQueryError::InputTooLarge,
    229                 SafeErrorCode::AvailabilityInvalidInput,
    230             ),
    231             (
    232                 AvailabilityQueryError::ScopeMismatch,
    233                 SafeErrorCode::AvailabilityScopeMismatch,
    234             ),
    235             (
    236                 AvailabilityQueryError::StaleQuery,
    237                 SafeErrorCode::AvailabilityStaleQuery,
    238             ),
    239             (
    240                 AvailabilityQueryError::Capacity,
    241                 SafeErrorCode::AvailabilityCapacity,
    242             ),
    243         ] {
    244             assert_eq!(SafeError::from(query).code(), code);
    245         }
    246     }
    247 }