lib

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

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 }