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 }