decode.rs (5679B)
1 #![cfg(feature = "json")] 2 #![forbid(unsafe_code)] 3 4 #[cfg(not(feature = "std"))] 5 use alloc::{ 6 string::{String, ToString}, 7 vec::Vec, 8 }; 9 10 use radroots_event::{envelope::kind::KIND_DOCUMENT, post::document::Document, tag::name::TAG_D}; 11 12 use crate::d_tag::validate_d_tag_tag; 13 use crate::error::EventParseError; 14 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent}; 15 16 const TAG_A: &str = "a"; 17 const TAG_P: &str = "p"; 18 const DEFAULT_KIND: u32 = KIND_DOCUMENT; 19 20 fn parse_d_tag(tags: &[Vec<String>]) -> Result<String, EventParseError> { 21 let tag = tags 22 .iter() 23 .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_D)) 24 .ok_or(EventParseError::MissingTag(TAG_D))?; 25 let value = tag 26 .get(1) 27 .map(|s| s.to_string()) 28 .ok_or(EventParseError::InvalidTag(TAG_D))?; 29 if value.trim().is_empty() { 30 return Err(EventParseError::InvalidTag(TAG_D)); 31 } 32 validate_d_tag_tag(&value, TAG_D)?; 33 Ok(value) 34 } 35 36 fn parse_subject_pubkey(tags: &[Vec<String>]) -> Result<String, EventParseError> { 37 let tag = tags 38 .iter() 39 .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_P)) 40 .ok_or(EventParseError::MissingTag(TAG_P))?; 41 let value = tag 42 .get(1) 43 .map(|s| s.to_string()) 44 .ok_or(EventParseError::InvalidTag(TAG_P))?; 45 if value.trim().is_empty() { 46 return Err(EventParseError::InvalidTag(TAG_P)); 47 } 48 Ok(value) 49 } 50 51 fn parse_subject_address(tags: &[Vec<String>]) -> Result<Option<String>, EventParseError> { 52 let tag = tags 53 .iter() 54 .find(|t| t.first().map(|s| s.as_str()) == Some(TAG_A)); 55 let Some(tag) = tag else { return Ok(None) }; 56 let value = tag 57 .get(1) 58 .map(|s| s.to_string()) 59 .ok_or(EventParseError::InvalidTag(TAG_A))?; 60 if value.trim().is_empty() { 61 return Err(EventParseError::InvalidTag(TAG_A)); 62 } 63 Ok(Some(value)) 64 } 65 66 pub fn document_from_event( 67 kind: u32, 68 tags: &[Vec<String>], 69 content: &str, 70 ) -> Result<Document, EventParseError> { 71 if kind != DEFAULT_KIND { 72 return Err(EventParseError::InvalidKind { 73 expected: "30361", 74 got: kind, 75 }); 76 } 77 if content.trim().is_empty() { 78 return Err(EventParseError::InvalidJson("content")); 79 } 80 let d_tag = parse_d_tag(tags)?; 81 let subject_pubkey = parse_subject_pubkey(tags)?; 82 let subject_address = parse_subject_address(tags)?; 83 let mut document: Document = 84 serde_json::from_str(content).map_err(|_| EventParseError::InvalidJson("content"))?; 85 86 if document.d_tag.trim().is_empty() { 87 document.d_tag = d_tag; 88 } else if document.d_tag != d_tag { 89 return Err(EventParseError::InvalidTag(TAG_D)); 90 } 91 92 if document.subject.pubkey.trim().is_empty() { 93 document.subject.pubkey = subject_pubkey; 94 } else if document.subject.pubkey != subject_pubkey { 95 return Err(EventParseError::InvalidTag(TAG_P)); 96 } 97 98 if let Some(address) = document.subject.address.as_ref() 99 && address.trim().is_empty() 100 { 101 return Err(EventParseError::InvalidTag(TAG_A)); 102 } 103 104 if let Some(tag_address) = subject_address { 105 match document.subject.address.as_ref() { 106 None => { 107 document.subject.address = Some(tag_address); 108 } 109 Some(existing) => { 110 if existing != &tag_address { 111 return Err(EventParseError::InvalidTag(TAG_A)); 112 } 113 } 114 } 115 } else if document.subject.address.is_some() { 116 return Err(EventParseError::MissingTag(TAG_A)); 117 } 118 119 Ok(document) 120 } 121 122 pub fn data_from_event( 123 id: String, 124 author: String, 125 published_at: u64, 126 kind: u32, 127 content: String, 128 tags: Vec<Vec<String>>, 129 ) -> Result<RadrootsParsedData<Document>, EventParseError> { 130 let document = document_from_event(kind, &tags, &content)?; 131 Ok(RadrootsParsedData::new( 132 id, 133 author, 134 published_at, 135 kind, 136 document, 137 )) 138 } 139 140 pub fn parsed_from_event( 141 id: String, 142 author: String, 143 published_at: u64, 144 kind: u32, 145 content: String, 146 tags: Vec<Vec<String>>, 147 sig: String, 148 ) -> Result<RadrootsParsedEvent<Document>, EventParseError> { 149 let data = data_from_event( 150 id.clone(), 151 author.clone(), 152 published_at, 153 kind, 154 content.clone(), 155 tags.clone(), 156 )?; 157 RadrootsParsedEvent::from_event_parts(id, author, published_at, kind, content, tags, sig, data) 158 } 159 160 #[cfg(test)] 161 mod tests { 162 use super::*; 163 use radroots_event::post::document::{Document, DocumentSubject}; 164 165 #[test] 166 fn document_decode_accepts_subject_without_address() { 167 let document = Document { 168 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 169 doc_type: "policy".to_string(), 170 title: "Farm policy".to_string(), 171 version: "1.0.0".to_string(), 172 summary: None, 173 effective_at: None, 174 body_markdown: None, 175 subject: DocumentSubject { 176 pubkey: "subject-pubkey".to_string(), 177 address: None, 178 }, 179 tags: None, 180 }; 181 let content = serde_json::to_string(&document).expect("document content"); 182 183 let decoded = document_from_event( 184 DEFAULT_KIND, 185 &[ 186 vec![TAG_D.to_string(), "AAAAAAAAAAAAAAAAAAAAAA".to_string()], 187 vec![TAG_P.to_string(), "subject-pubkey".to_string()], 188 ], 189 &content, 190 ) 191 .expect("document"); 192 193 assert_eq!(decoded.subject.address, None); 194 } 195 }