lib

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

error.rs (17229B)


      1 use core::fmt;
      2 
      3 #[derive(Clone, Debug, PartialEq, Eq)]
      4 #[non_exhaustive]
      5 pub enum Error {
      6     InvalidSha256,
      7     InvalidFileExtension,
      8     InvalidHashPath,
      9     InvalidBlobUrl,
     10     UnsupportedBlobUrlScheme,
     11     BlobUrlCredentialsForbidden,
     12     BlobUrlQueryForbidden,
     13     BlobUrlFragmentForbidden,
     14     InsecureBlobUrl,
     15     DescriptorExtensionRequired,
     16     DescriptorHashMismatch,
     17     InvalidMediaType,
     18     BlobHashMismatch,
     19     BlobSizeMismatch { expected: u64, actual: u64 },
     20     BlobMediaTypeMismatch,
     21     InvalidAuthorizationContent,
     22     InvalidAuthorizationAction,
     23     InvalidAuthorizationServerDomain,
     24     MissingAuthorizationActionTag,
     25     DuplicateAuthorizationActionTag,
     26     MalformedAuthorizationActionTag,
     27     MissingAuthorizationExpirationTag,
     28     DuplicateAuthorizationExpirationTag,
     29     MalformedAuthorizationExpirationTag,
     30     MalformedAuthorizationServerTag,
     31     MalformedAuthorizationHashTag,
     32     InvalidAuthorizationCreatedAge,
     33     InvalidAuthorizationLifetime,
     34     AuthorizationTimestampOverflow,
     35     AuthorizationCreatedInFuture,
     36     AuthorizationStale,
     37     AuthorizationExpired,
     38     AuthorizationActionMismatch,
     39     AuthorizationServerRequired,
     40     AuthorizationServerMismatch,
     41     AuthorizationHashRequired,
     42     AuthorizationHashMismatch,
     43 }
     44 
     45 impl Error {
     46     pub const fn code(&self) -> &'static str {
     47         match self {
     48             Self::InvalidSha256 => "invalid_sha256",
     49             Self::InvalidFileExtension => "invalid_file_extension",
     50             Self::InvalidHashPath => "invalid_hash_path",
     51             Self::InvalidBlobUrl => "invalid_blob_url",
     52             Self::UnsupportedBlobUrlScheme => "unsupported_blob_url_scheme",
     53             Self::BlobUrlCredentialsForbidden => "blob_url_credentials_forbidden",
     54             Self::BlobUrlQueryForbidden => "blob_url_query_forbidden",
     55             Self::BlobUrlFragmentForbidden => "blob_url_fragment_forbidden",
     56             Self::InsecureBlobUrl => "insecure_blob_url",
     57             Self::DescriptorExtensionRequired => "descriptor_extension_required",
     58             Self::DescriptorHashMismatch => "descriptor_hash_mismatch",
     59             Self::InvalidMediaType => "invalid_media_type",
     60             Self::BlobHashMismatch => "blob_hash_mismatch",
     61             Self::BlobSizeMismatch { .. } => "blob_size_mismatch",
     62             Self::BlobMediaTypeMismatch => "blob_media_type_mismatch",
     63             Self::InvalidAuthorizationContent => "invalid_authorization_content",
     64             Self::InvalidAuthorizationAction => "invalid_authorization_action",
     65             Self::InvalidAuthorizationServerDomain => "invalid_authorization_server_domain",
     66             Self::MissingAuthorizationActionTag => "missing_authorization_action_tag",
     67             Self::DuplicateAuthorizationActionTag => "duplicate_authorization_action_tag",
     68             Self::MalformedAuthorizationActionTag => "malformed_authorization_action_tag",
     69             Self::MissingAuthorizationExpirationTag => "missing_authorization_expiration_tag",
     70             Self::DuplicateAuthorizationExpirationTag => "duplicate_authorization_expiration_tag",
     71             Self::MalformedAuthorizationExpirationTag => "malformed_authorization_expiration_tag",
     72             Self::MalformedAuthorizationServerTag => "malformed_authorization_server_tag",
     73             Self::MalformedAuthorizationHashTag => "malformed_authorization_hash_tag",
     74             Self::InvalidAuthorizationCreatedAge => "invalid_authorization_created_age",
     75             Self::InvalidAuthorizationLifetime => "invalid_authorization_lifetime",
     76             Self::AuthorizationTimestampOverflow => "authorization_timestamp_overflow",
     77             Self::AuthorizationCreatedInFuture => "authorization_created_in_future",
     78             Self::AuthorizationStale => "authorization_stale",
     79             Self::AuthorizationExpired => "authorization_expired",
     80             Self::AuthorizationActionMismatch => "authorization_action_mismatch",
     81             Self::AuthorizationServerRequired => "authorization_server_required",
     82             Self::AuthorizationServerMismatch => "authorization_server_mismatch",
     83             Self::AuthorizationHashRequired => "authorization_hash_required",
     84             Self::AuthorizationHashMismatch => "authorization_hash_mismatch",
     85         }
     86     }
     87 }
     88 
     89 impl fmt::Display for Error {
     90     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     91         match self {
     92             Self::InvalidSha256 => {
     93                 f.write_str("sha256 must be 64 lowercase hexadecimal characters")
     94             }
     95             Self::InvalidFileExtension => f.write_str("invalid Blossom file extension"),
     96             Self::InvalidHashPath => f.write_str("invalid Blossom root hash path"),
     97             Self::InvalidBlobUrl => f.write_str("invalid Blossom blob URL"),
     98             Self::UnsupportedBlobUrlScheme => {
     99                 f.write_str("Blossom blob URL scheme must be http or https")
    100             }
    101             Self::BlobUrlCredentialsForbidden => {
    102                 f.write_str("Blossom blob URL credentials are forbidden")
    103             }
    104             Self::BlobUrlQueryForbidden => f.write_str("Blossom blob URL query is forbidden"),
    105             Self::BlobUrlFragmentForbidden => f.write_str("Blossom blob URL fragment is forbidden"),
    106             Self::InsecureBlobUrl => {
    107                 f.write_str("Radroots blob references require HTTPS or loopback HTTP")
    108             }
    109             Self::DescriptorExtensionRequired => {
    110                 f.write_str("BUD-02 descriptor URL requires a file extension")
    111             }
    112             Self::DescriptorHashMismatch => {
    113                 f.write_str("descriptor URL hash does not match descriptor sha256")
    114             }
    115             Self::InvalidMediaType => f.write_str("invalid media type"),
    116             Self::BlobHashMismatch => f.write_str("blob bytes do not match descriptor sha256"),
    117             Self::BlobSizeMismatch { expected, actual } => {
    118                 write!(f, "blob size mismatch: expected {expected}, got {actual}")
    119             }
    120             Self::BlobMediaTypeMismatch => {
    121                 f.write_str("descriptor media type does not match the approved media type")
    122             }
    123             Self::InvalidAuthorizationContent => {
    124                 f.write_str("Blossom authorization content must be bounded human-readable text")
    125             }
    126             Self::InvalidAuthorizationAction => f.write_str("invalid Blossom authorization action"),
    127             Self::InvalidAuthorizationServerDomain => {
    128                 f.write_str("invalid Blossom authorization server domain")
    129             }
    130             Self::MissingAuthorizationActionTag => {
    131                 f.write_str("Blossom authorization is missing a t action tag")
    132             }
    133             Self::DuplicateAuthorizationActionTag => {
    134                 f.write_str("Blossom authorization has more than one t action tag")
    135             }
    136             Self::MalformedAuthorizationActionTag => {
    137                 f.write_str("malformed Blossom authorization t action tag")
    138             }
    139             Self::MissingAuthorizationExpirationTag => {
    140                 f.write_str("Blossom authorization is missing an expiration tag")
    141             }
    142             Self::DuplicateAuthorizationExpirationTag => {
    143                 f.write_str("Blossom authorization has more than one expiration tag")
    144             }
    145             Self::MalformedAuthorizationExpirationTag => {
    146                 f.write_str("malformed Blossom authorization expiration tag")
    147             }
    148             Self::MalformedAuthorizationServerTag => {
    149                 f.write_str("malformed Blossom authorization server tag")
    150             }
    151             Self::MalformedAuthorizationHashTag => {
    152                 f.write_str("malformed Blossom authorization x hash tag")
    153             }
    154             Self::InvalidAuthorizationCreatedAge => {
    155                 f.write_str("Blossom authorization maximum created age must not exceed 300 seconds")
    156             }
    157             Self::InvalidAuthorizationLifetime => {
    158                 f.write_str("Blossom authorization lifetime must be between 1 and 300 seconds")
    159             }
    160             Self::AuthorizationTimestampOverflow => {
    161                 f.write_str("Blossom authorization expiration timestamp overflows u64")
    162             }
    163             Self::AuthorizationCreatedInFuture => {
    164                 f.write_str("Blossom authorization must be created in the past")
    165             }
    166             Self::AuthorizationStale => {
    167                 f.write_str("Blossom authorization is outside the accepted creation-age window")
    168             }
    169             Self::AuthorizationExpired => f.write_str("Blossom authorization is expired"),
    170             Self::AuthorizationActionMismatch => {
    171                 f.write_str("Blossom authorization action does not match the target endpoint")
    172             }
    173             Self::AuthorizationServerRequired => {
    174                 f.write_str("Blossom authorization requires a server scope")
    175             }
    176             Self::AuthorizationServerMismatch => {
    177                 f.write_str("Blossom authorization does not include the target server")
    178             }
    179             Self::AuthorizationHashRequired => {
    180                 f.write_str("Blossom authorization requires an x hash scope")
    181             }
    182             Self::AuthorizationHashMismatch => {
    183                 f.write_str("Blossom authorization does not include the target blob hash")
    184             }
    185         }
    186     }
    187 }
    188 
    189 #[cfg(feature = "std")]
    190 impl std::error::Error for Error {}
    191 
    192 #[cfg(test)]
    193 mod tests {
    194     use super::Error;
    195     use alloc::format;
    196 
    197     #[test]
    198     fn error_codes_and_messages_are_stable() {
    199         let cases = [
    200             (
    201                 Error::InvalidSha256,
    202                 "invalid_sha256",
    203                 "sha256 must be 64 lowercase hexadecimal characters",
    204             ),
    205             (
    206                 Error::InvalidFileExtension,
    207                 "invalid_file_extension",
    208                 "invalid Blossom file extension",
    209             ),
    210             (
    211                 Error::InvalidHashPath,
    212                 "invalid_hash_path",
    213                 "invalid Blossom root hash path",
    214             ),
    215             (
    216                 Error::InvalidBlobUrl,
    217                 "invalid_blob_url",
    218                 "invalid Blossom blob URL",
    219             ),
    220             (
    221                 Error::UnsupportedBlobUrlScheme,
    222                 "unsupported_blob_url_scheme",
    223                 "Blossom blob URL scheme must be http or https",
    224             ),
    225             (
    226                 Error::BlobUrlCredentialsForbidden,
    227                 "blob_url_credentials_forbidden",
    228                 "Blossom blob URL credentials are forbidden",
    229             ),
    230             (
    231                 Error::BlobUrlQueryForbidden,
    232                 "blob_url_query_forbidden",
    233                 "Blossom blob URL query is forbidden",
    234             ),
    235             (
    236                 Error::BlobUrlFragmentForbidden,
    237                 "blob_url_fragment_forbidden",
    238                 "Blossom blob URL fragment is forbidden",
    239             ),
    240             (
    241                 Error::InsecureBlobUrl,
    242                 "insecure_blob_url",
    243                 "Radroots blob references require HTTPS or loopback HTTP",
    244             ),
    245             (
    246                 Error::DescriptorExtensionRequired,
    247                 "descriptor_extension_required",
    248                 "BUD-02 descriptor URL requires a file extension",
    249             ),
    250             (
    251                 Error::DescriptorHashMismatch,
    252                 "descriptor_hash_mismatch",
    253                 "descriptor URL hash does not match descriptor sha256",
    254             ),
    255             (
    256                 Error::InvalidMediaType,
    257                 "invalid_media_type",
    258                 "invalid media type",
    259             ),
    260             (
    261                 Error::BlobHashMismatch,
    262                 "blob_hash_mismatch",
    263                 "blob bytes do not match descriptor sha256",
    264             ),
    265             (
    266                 Error::BlobSizeMismatch {
    267                     expected: 1,
    268                     actual: 2,
    269                 },
    270                 "blob_size_mismatch",
    271                 "blob size mismatch: expected 1, got 2",
    272             ),
    273             (
    274                 Error::BlobMediaTypeMismatch,
    275                 "blob_media_type_mismatch",
    276                 "descriptor media type does not match the approved media type",
    277             ),
    278         ];
    279         for (error, code, message) in cases {
    280             assert_eq!(error.code(), code);
    281             assert_eq!(format!("{error}"), message);
    282         }
    283     }
    284 
    285     #[test]
    286     fn authorization_error_codes_and_messages_are_stable() {
    287         let cases = [
    288             (
    289                 Error::InvalidAuthorizationContent,
    290                 "invalid_authorization_content",
    291                 "Blossom authorization content must be bounded human-readable text",
    292             ),
    293             (
    294                 Error::InvalidAuthorizationAction,
    295                 "invalid_authorization_action",
    296                 "invalid Blossom authorization action",
    297             ),
    298             (
    299                 Error::InvalidAuthorizationServerDomain,
    300                 "invalid_authorization_server_domain",
    301                 "invalid Blossom authorization server domain",
    302             ),
    303             (
    304                 Error::MissingAuthorizationActionTag,
    305                 "missing_authorization_action_tag",
    306                 "Blossom authorization is missing a t action tag",
    307             ),
    308             (
    309                 Error::DuplicateAuthorizationActionTag,
    310                 "duplicate_authorization_action_tag",
    311                 "Blossom authorization has more than one t action tag",
    312             ),
    313             (
    314                 Error::MalformedAuthorizationActionTag,
    315                 "malformed_authorization_action_tag",
    316                 "malformed Blossom authorization t action tag",
    317             ),
    318             (
    319                 Error::MissingAuthorizationExpirationTag,
    320                 "missing_authorization_expiration_tag",
    321                 "Blossom authorization is missing an expiration tag",
    322             ),
    323             (
    324                 Error::DuplicateAuthorizationExpirationTag,
    325                 "duplicate_authorization_expiration_tag",
    326                 "Blossom authorization has more than one expiration tag",
    327             ),
    328             (
    329                 Error::MalformedAuthorizationExpirationTag,
    330                 "malformed_authorization_expiration_tag",
    331                 "malformed Blossom authorization expiration tag",
    332             ),
    333             (
    334                 Error::MalformedAuthorizationServerTag,
    335                 "malformed_authorization_server_tag",
    336                 "malformed Blossom authorization server tag",
    337             ),
    338             (
    339                 Error::MalformedAuthorizationHashTag,
    340                 "malformed_authorization_hash_tag",
    341                 "malformed Blossom authorization x hash tag",
    342             ),
    343             (
    344                 Error::InvalidAuthorizationCreatedAge,
    345                 "invalid_authorization_created_age",
    346                 "Blossom authorization maximum created age must not exceed 300 seconds",
    347             ),
    348             (
    349                 Error::InvalidAuthorizationLifetime,
    350                 "invalid_authorization_lifetime",
    351                 "Blossom authorization lifetime must be between 1 and 300 seconds",
    352             ),
    353             (
    354                 Error::AuthorizationTimestampOverflow,
    355                 "authorization_timestamp_overflow",
    356                 "Blossom authorization expiration timestamp overflows u64",
    357             ),
    358             (
    359                 Error::AuthorizationCreatedInFuture,
    360                 "authorization_created_in_future",
    361                 "Blossom authorization must be created in the past",
    362             ),
    363             (
    364                 Error::AuthorizationStale,
    365                 "authorization_stale",
    366                 "Blossom authorization is outside the accepted creation-age window",
    367             ),
    368             (
    369                 Error::AuthorizationExpired,
    370                 "authorization_expired",
    371                 "Blossom authorization is expired",
    372             ),
    373             (
    374                 Error::AuthorizationActionMismatch,
    375                 "authorization_action_mismatch",
    376                 "Blossom authorization action does not match the target endpoint",
    377             ),
    378             (
    379                 Error::AuthorizationServerRequired,
    380                 "authorization_server_required",
    381                 "Blossom authorization requires a server scope",
    382             ),
    383             (
    384                 Error::AuthorizationServerMismatch,
    385                 "authorization_server_mismatch",
    386                 "Blossom authorization does not include the target server",
    387             ),
    388             (
    389                 Error::AuthorizationHashRequired,
    390                 "authorization_hash_required",
    391                 "Blossom authorization requires an x hash scope",
    392             ),
    393             (
    394                 Error::AuthorizationHashMismatch,
    395                 "authorization_hash_mismatch",
    396                 "Blossom authorization does not include the target blob hash",
    397             ),
    398         ];
    399         for (error, code, message) in cases {
    400             assert_eq!(error.code(), code);
    401             assert_eq!(format!("{error}"), message);
    402         }
    403     }
    404 }