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 }