lib

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

encode.rs (6381B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     format,
      4     string::{String, ToString},
      5     vec,
      6     vec::Vec,
      7 };
      8 
      9 use radroots_events::{
     10     farm_crdt::RadrootsFarmCrdtDocumentKind,
     11     farm_file::{
     12         KIND_FARM_FILE_METADATA, RadrootsFarmFileDimensions, RadrootsFarmFileMetadata,
     13         RadrootsFarmFileSource,
     14     },
     15     farm_workspace::KIND_FARM_WORKSPACE_MANIFEST,
     16     tags::{TAG_A, TAG_D, TAG_H, TAG_MIME, TAG_ORIGINAL_SHA256, TAG_SHA256, TAG_URL},
     17 };
     18 
     19 use crate::d_tag::validate_d_tag;
     20 use crate::error::EventEncodeError;
     21 use crate::field_helpers::{
     22     address_string, push_optional_tag, push_tag, push_tag_values, validate_lowercase_hex_64,
     23     validate_non_empty_field,
     24 };
     25 use crate::wire::WireEventParts;
     26 
     27 const TAG_ALT: &str = "alt";
     28 const TAG_BLURHASH: &str = "blurhash";
     29 const TAG_DIMENSIONS: &str = "dim";
     30 const TAG_FALLBACK: &str = "fallback";
     31 const TAG_IMAGE: &str = "image";
     32 const TAG_OWNER_DOCUMENT: &str = "radroots:owner_document";
     33 const TAG_SIZE: &str = "size";
     34 const TAG_THUMB: &str = "thumb";
     35 
     36 pub fn farm_file_metadata_build_tags(
     37     metadata: &RadrootsFarmFileMetadata,
     38 ) -> Result<Vec<Vec<String>>, EventEncodeError> {
     39     validate_metadata(metadata)?;
     40     let workspace = address_string(
     41         KIND_FARM_WORKSPACE_MANIFEST,
     42         &metadata.workspace.pubkey,
     43         &metadata.workspace.d_tag,
     44         "workspace",
     45     )?;
     46     let mut tags = Vec::new();
     47     push_tag(&mut tags, TAG_D, metadata.d_tag.as_str());
     48     push_tag(&mut tags, TAG_H, metadata.farm_group_id.as_str());
     49     push_tag(&mut tags, TAG_A, workspace);
     50     push_tag(&mut tags, TAG_URL, metadata.url.as_str());
     51     push_tag(&mut tags, TAG_MIME, metadata.mime_type.as_str());
     52     push_tag(&mut tags, TAG_SHA256, metadata.sha256.as_str());
     53     push_tag_values(
     54         &mut tags,
     55         TAG_OWNER_DOCUMENT,
     56         vec![
     57             metadata.owner_document_id.clone(),
     58             document_kind_tag(&metadata.owner_document_kind),
     59         ],
     60     );
     61     push_optional_tag(
     62         &mut tags,
     63         TAG_ORIGINAL_SHA256,
     64         metadata.original_sha256.as_deref(),
     65     );
     66     if let Some(size) = metadata.size_bytes {
     67         push_tag(&mut tags, TAG_SIZE, size.to_string());
     68     }
     69     if let Some(dimensions) = metadata.dimensions {
     70         push_tag(&mut tags, TAG_DIMENSIONS, dimensions_tag(dimensions));
     71     }
     72     push_optional_tag(&mut tags, TAG_BLURHASH, metadata.blurhash.as_deref());
     73     push_source_tag(&mut tags, TAG_THUMB, metadata.thumb.as_ref())?;
     74     push_source_tag(&mut tags, TAG_IMAGE, metadata.image.as_ref())?;
     75     push_optional_tag(&mut tags, TAG_ALT, metadata.alt.as_deref());
     76     for fallback in &metadata.fallbacks {
     77         validate_non_empty_field(fallback, "fallbacks")?;
     78         push_tag(&mut tags, TAG_FALLBACK, fallback.clone());
     79     }
     80     Ok(tags)
     81 }
     82 
     83 pub fn to_wire_parts(
     84     metadata: &RadrootsFarmFileMetadata,
     85 ) -> Result<WireEventParts, EventEncodeError> {
     86     to_wire_parts_with_kind(metadata, KIND_FARM_FILE_METADATA)
     87 }
     88 
     89 pub fn to_wire_parts_with_kind(
     90     metadata: &RadrootsFarmFileMetadata,
     91     kind: u32,
     92 ) -> Result<WireEventParts, EventEncodeError> {
     93     if kind != KIND_FARM_FILE_METADATA {
     94         return Err(EventEncodeError::InvalidKind(kind));
     95     }
     96     let tags = farm_file_metadata_build_tags(metadata)?;
     97     Ok(WireEventParts {
     98         kind,
     99         content: metadata.caption.clone().unwrap_or_default(),
    100         tags,
    101     })
    102 }
    103 
    104 pub(crate) fn validate_metadata(
    105     metadata: &RadrootsFarmFileMetadata,
    106 ) -> Result<(), EventEncodeError> {
    107     validate_d_tag(&metadata.d_tag, "d_tag")?;
    108     validate_non_empty_field(&metadata.farm_group_id, "farm_group_id")?;
    109     validate_non_empty_field(&metadata.workspace.pubkey, "workspace.pubkey")?;
    110     validate_d_tag(&metadata.workspace.d_tag, "workspace.d_tag")?;
    111     validate_d_tag(&metadata.owner_document_id, "owner_document_id")?;
    112     validate_non_empty_field(&metadata.url, "url")?;
    113     validate_non_empty_field(&metadata.mime_type, "mime_type")?;
    114     validate_lowercase_hex_64(&metadata.sha256, "sha256")?;
    115     if let Some(hash) = metadata.original_sha256.as_deref() {
    116         validate_lowercase_hex_64(hash, "original_sha256")?;
    117     }
    118     if let Some(caption) = metadata.caption.as_deref() {
    119         validate_non_empty_field(caption, "caption")?;
    120     }
    121     if let Some(dimensions) = metadata.dimensions {
    122         validate_dimensions(dimensions, "dimensions")?;
    123     }
    124     if let Some(blurhash) = metadata.blurhash.as_deref() {
    125         validate_non_empty_field(blurhash, "blurhash")?;
    126     }
    127     validate_source(metadata.thumb.as_ref(), "thumb")?;
    128     validate_source(metadata.image.as_ref(), "image")?;
    129     if let Some(alt) = metadata.alt.as_deref() {
    130         validate_non_empty_field(alt, "alt")?;
    131     }
    132     for fallback in &metadata.fallbacks {
    133         validate_non_empty_field(fallback, "fallbacks")?;
    134     }
    135     Ok(())
    136 }
    137 
    138 pub(crate) fn document_kind_tag(kind: &RadrootsFarmCrdtDocumentKind) -> String {
    139     kind.as_str().to_string()
    140 }
    141 
    142 fn validate_dimensions(
    143     dimensions: RadrootsFarmFileDimensions,
    144     field: &'static str,
    145 ) -> Result<(), EventEncodeError> {
    146     if dimensions.w == 0 || dimensions.h == 0 {
    147         Err(EventEncodeError::InvalidField(field))
    148     } else {
    149         Ok(())
    150     }
    151 }
    152 
    153 fn validate_source(
    154     source: Option<&RadrootsFarmFileSource>,
    155     field: &'static str,
    156 ) -> Result<(), EventEncodeError> {
    157     let Some(source) = source else {
    158         return Ok(());
    159     };
    160     validate_non_empty_field(&source.url, field)?;
    161     if let Some(mime_type) = source.mime_type.as_deref() {
    162         validate_non_empty_field(mime_type, field)?;
    163     }
    164     if let Some(dimensions) = source.dimensions {
    165         validate_dimensions(dimensions, field)?;
    166     }
    167     Ok(())
    168 }
    169 
    170 fn push_source_tag(
    171     tags: &mut Vec<Vec<String>>,
    172     key: &'static str,
    173     source: Option<&RadrootsFarmFileSource>,
    174 ) -> Result<(), EventEncodeError> {
    175     let Some(source) = source else {
    176         return Ok(());
    177     };
    178     validate_source(Some(source), key)?;
    179     let mut values = vec![source.url.clone()];
    180     if let Some(mime_type) = source.mime_type.as_deref() {
    181         values.push(mime_type.to_string());
    182     }
    183     if let Some(dimensions) = source.dimensions {
    184         values.push(dimensions_tag(dimensions));
    185     }
    186     push_tag_values(tags, key, values);
    187     Ok(())
    188 }
    189 
    190 fn dimensions_tag(dimensions: RadrootsFarmFileDimensions) -> String {
    191     format!("{}x{}", dimensions.w, dimensions.h)
    192 }