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 }