media.rs (3500B)
1 use core::fmt; 2 3 #[path = "file_metadata.rs"] 4 pub mod file_metadata; 5 6 use radroots_blossom::ByteVerifiedDescriptor; 7 8 /// Errors raised while constructing strict authored image media. 9 #[non_exhaustive] 10 #[derive(Clone, Debug, PartialEq, Eq)] 11 pub enum AuthoredImageError { 12 MediaTypeNotImage, 13 } 14 15 impl AuthoredImageError { 16 pub const fn code(&self) -> &'static str { 17 match self { 18 Self::MediaTypeNotImage => "media_type_not_image", 19 } 20 } 21 } 22 23 impl fmt::Display for AuthoredImageError { 24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 match self { 26 Self::MediaTypeNotImage => { 27 f.write_str("authored image descriptor must have an image media type") 28 } 29 } 30 } 31 } 32 33 #[cfg(feature = "std")] 34 impl std::error::Error for AuthoredImageError {} 35 36 /// A byte-verified Blossom descriptor whose declared media type is `image/*`. 37 /// 38 /// Construction requires the non-forgeable Blossom byte-verification state. 39 /// This typestate does not prove upload completion, network availability, or 40 /// image format safety. Owning runtimes remain responsible for those policies. 41 #[derive(Clone, Debug, PartialEq, Eq)] 42 pub struct AuthoredImage(ByteVerifiedDescriptor); 43 44 impl AuthoredImage { 45 pub fn try_from_verified_descriptor( 46 descriptor: ByteVerifiedDescriptor, 47 ) -> Result<Self, AuthoredImageError> { 48 if !descriptor.media_type().as_str().starts_with("image/") { 49 return Err(AuthoredImageError::MediaTypeNotImage); 50 } 51 Ok(Self(descriptor)) 52 } 53 54 pub fn descriptor(&self) -> &ByteVerifiedDescriptor { 55 &self.0 56 } 57 } 58 59 impl TryFrom<ByteVerifiedDescriptor> for AuthoredImage { 60 type Error = AuthoredImageError; 61 62 fn try_from(value: ByteVerifiedDescriptor) -> Result<Self, Self::Error> { 63 Self::try_from_verified_descriptor(value) 64 } 65 } 66 67 #[cfg(test)] 68 #[cfg_attr(coverage_nightly, coverage(off))] 69 mod tests { 70 use super::*; 71 use radroots_blossom::{BlobDescriptor, BlobUrl, MediaType, Sha256}; 72 73 #[test] 74 fn authored_image_requires_a_byte_verified_image_descriptor() { 75 let descriptor = verified_descriptor("image/webp", "webp"); 76 let expected_url = descriptor.url().as_str().to_owned(); 77 let image = AuthoredImage::try_from(descriptor).unwrap(); 78 79 assert_eq!(image.descriptor().url().as_str(), expected_url); 80 } 81 82 #[test] 83 fn authored_image_rejects_non_image_media_types_with_a_stable_error() { 84 let error = 85 AuthoredImage::try_from_verified_descriptor(verified_descriptor("text/plain", "txt")) 86 .unwrap_err(); 87 88 assert_eq!(error, AuthoredImageError::MediaTypeNotImage); 89 assert_eq!(error.code(), "media_type_not_image"); 90 assert_eq!( 91 error.to_string(), 92 "authored image descriptor must have an image media type" 93 ); 94 } 95 96 fn verified_descriptor(media_type: &str, extension: &str) -> ByteVerifiedDescriptor { 97 let bytes = b"hello"; 98 let hash = Sha256::digest(bytes); 99 let media_type = MediaType::parse(media_type).unwrap(); 100 BlobDescriptor::new( 101 BlobUrl::parse(&format!("https://media.example/{hash}.{extension}")).unwrap(), 102 hash, 103 bytes.len() as u64, 104 media_type.clone(), 105 1_784_347_200, 106 ) 107 .unwrap() 108 .approve_reference() 109 .unwrap() 110 .verify_bytes(bytes, &media_type) 111 .unwrap() 112 } 113 }