lib

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

decode.rs (4990B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     string::{String, ToString},
      4     vec::Vec,
      5 };
      6 
      7 use radroots_event::{
      8     envelope::kind::KIND_PUBLIC_FILE_METADATA,
      9     media::file_metadata::FileMetadata,
     10     social::SocialMediaThumbnail,
     11     tag::name::{
     12         TAG_ALT, TAG_BLURHASH, TAG_DIMENSIONS, TAG_FALLBACK, TAG_MAGNET, TAG_MIME,
     13         TAG_ORIGINAL_SHA256, TAG_SERVICE, TAG_SHA256, TAG_SIZE, TAG_SUMMARY, TAG_THUMB, TAG_URL,
     14     },
     15 };
     16 
     17 use crate::error::EventParseError;
     18 use crate::field_helpers::{
     19     optional_tag_value, required_tag_value, tag_values, validate_lowercase_hex_64_tag,
     20 };
     21 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent};
     22 use crate::social_helpers::{first_tag_value, parse_dimensions_tag};
     23 
     24 const EXPECTED_KIND: &str = "1063";
     25 const TAG_RADROOTS_OWNER_DOCUMENT: &str = "radroots:owner_document";
     26 
     27 pub fn file_metadata_from_event(
     28     kind: u32,
     29     tags: &[Vec<String>],
     30     content: &str,
     31 ) -> Result<FileMetadata, EventParseError> {
     32     if kind != KIND_PUBLIC_FILE_METADATA {
     33         return Err(EventParseError::InvalidKind {
     34             expected: EXPECTED_KIND,
     35             got: kind,
     36         });
     37     }
     38     reject_private_farm_file_tags(tags)?;
     39     let url = required_tag_value(tags, TAG_URL)?;
     40     let mime_type = required_tag_value(tags, TAG_MIME)?;
     41     let sha256 = required_tag_value(tags, TAG_SHA256)?;
     42     validate_lowercase_hex_64_tag(&sha256, TAG_SHA256)?;
     43     let original_sha256 = optional_hash_tag(tags, TAG_ORIGINAL_SHA256)?;
     44     let size = optional_tag_value(tags, TAG_SIZE)?
     45         .map(|value| {
     46             value
     47                 .parse::<u64>()
     48                 .map_err(|err| EventParseError::InvalidNumber(TAG_SIZE, err))
     49         })
     50         .transpose()?;
     51     let dimensions = optional_tag_value(tags, TAG_DIMENSIONS)?
     52         .map(|value| parse_dimensions_tag(&value, TAG_DIMENSIONS))
     53         .transpose()?;
     54 
     55     Ok(FileMetadata {
     56         url,
     57         mime_type,
     58         sha256,
     59         original_sha256,
     60         size,
     61         dimensions,
     62         blurhash: first_tag_value(tags, TAG_BLURHASH),
     63         thumbnails: parse_thumbnails(tags)?,
     64         summary: first_tag_value(tags, TAG_SUMMARY),
     65         alt: first_tag_value(tags, TAG_ALT),
     66         fallback: first_tag_value(tags, TAG_FALLBACK),
     67         magnet: first_tag_value(tags, TAG_MAGNET),
     68         content_hashes: non_empty_vec(tag_values(tags, "i")?),
     69         services: non_empty_vec(tag_values(tags, TAG_SERVICE)?),
     70         content: if content.is_empty() {
     71             None
     72         } else {
     73             Some(content.to_string())
     74         },
     75     })
     76 }
     77 
     78 fn reject_private_farm_file_tags(tags: &[Vec<String>]) -> Result<(), EventParseError> {
     79     if tags
     80         .iter()
     81         .any(|tag| tag.first().map(|value| value.as_str()) == Some(TAG_RADROOTS_OWNER_DOCUMENT))
     82     {
     83         Err(EventParseError::InvalidTag(TAG_RADROOTS_OWNER_DOCUMENT))
     84     } else {
     85         Ok(())
     86     }
     87 }
     88 
     89 pub fn data_from_event(
     90     id: String,
     91     author: String,
     92     published_at: u64,
     93     kind: u32,
     94     content: String,
     95     tags: Vec<Vec<String>>,
     96 ) -> Result<RadrootsParsedData<FileMetadata>, EventParseError> {
     97     let metadata = file_metadata_from_event(kind, &tags, &content)?;
     98     Ok(RadrootsParsedData::new(
     99         id,
    100         author,
    101         published_at,
    102         kind,
    103         metadata,
    104     ))
    105 }
    106 
    107 pub fn parsed_from_event(
    108     id: String,
    109     author: String,
    110     published_at: u64,
    111     kind: u32,
    112     content: String,
    113     tags: Vec<Vec<String>>,
    114     sig: String,
    115 ) -> Result<RadrootsParsedEvent<FileMetadata>, EventParseError> {
    116     let data = data_from_event(
    117         id.clone(),
    118         author.clone(),
    119         published_at,
    120         kind,
    121         content.clone(),
    122         tags.clone(),
    123     )?;
    124     RadrootsParsedEvent::from_event_parts(id, author, published_at, kind, content, tags, sig, data)
    125 }
    126 
    127 fn optional_hash_tag(
    128     tags: &[Vec<String>],
    129     key: &'static str,
    130 ) -> Result<Option<String>, EventParseError> {
    131     let Some(value) = optional_tag_value(tags, key)? else {
    132         return Ok(None);
    133     };
    134     validate_lowercase_hex_64_tag(&value, key)?;
    135     Ok(Some(value))
    136 }
    137 
    138 fn parse_thumbnails(
    139     tags: &[Vec<String>],
    140 ) -> Result<Option<Vec<SocialMediaThumbnail>>, EventParseError> {
    141     let thumbnails = tags
    142         .iter()
    143         .filter(|tag| tag.first().map(|value| value.as_str()) == Some(TAG_THUMB))
    144         .map(|tag| {
    145             let url = tag
    146                 .get(1)
    147                 .cloned()
    148                 .ok_or(EventParseError::InvalidTag(TAG_THUMB))?;
    149             let dimensions = tag
    150                 .get(2)
    151                 .filter(|value| !value.trim().is_empty())
    152                 .map(|value| parse_dimensions_tag(value, TAG_THUMB))
    153                 .transpose()?;
    154             Ok(SocialMediaThumbnail { url, dimensions })
    155         })
    156         .collect::<Result<Vec<_>, EventParseError>>()?;
    157     Ok(non_empty_vec(thumbnails))
    158 }
    159 
    160 fn non_empty_vec<T>(values: Vec<T>) -> Option<Vec<T>> {
    161     if values.is_empty() {
    162         None
    163     } else {
    164         Some(values)
    165     }
    166 }