lib

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

encode.rs (6230B)


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