encode.rs (4503B)
1 #![forbid(unsafe_code)] 2 3 #[cfg(not(feature = "std"))] 4 use alloc::{ 5 string::{String, ToString}, 6 vec::Vec, 7 }; 8 9 use radroots_events::{document::RadrootsDocument, tags::TAG_D}; 10 11 use crate::d_tag::validate_d_tag; 12 use crate::error::EventEncodeError; 13 14 #[cfg(feature = "serde_json")] 15 use radroots_events::kinds::KIND_DOCUMENT; 16 17 #[cfg(feature = "serde_json")] 18 use crate::wire::WireEventParts; 19 20 const TAG_T: &str = "t"; 21 const TAG_P: &str = "p"; 22 const TAG_A: &str = "a"; 23 24 fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { 25 tags.push(vec![key.to_string(), value.to_string()]); 26 } 27 28 pub fn document_build_tags( 29 document: &RadrootsDocument, 30 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 31 if document.d_tag.trim().is_empty() { 32 return Err(EventEncodeError::EmptyRequiredField("d_tag")); 33 } 34 validate_d_tag(&document.d_tag, "d_tag")?; 35 if document.doc_type.trim().is_empty() { 36 return Err(EventEncodeError::EmptyRequiredField("doc_type")); 37 } 38 if document.title.trim().is_empty() { 39 return Err(EventEncodeError::EmptyRequiredField("title")); 40 } 41 if document.version.trim().is_empty() { 42 return Err(EventEncodeError::EmptyRequiredField("version")); 43 } 44 if document.subject.pubkey.trim().is_empty() { 45 return Err(EventEncodeError::EmptyRequiredField("subject.pubkey")); 46 } 47 let mut tags = Vec::new(); 48 push_tag(&mut tags, TAG_D, &document.d_tag); 49 push_tag(&mut tags, TAG_P, &document.subject.pubkey); 50 if let Some(address) = document.subject.address.as_ref() { 51 let address = address.trim(); 52 if address.is_empty() { 53 return Err(EventEncodeError::EmptyRequiredField("subject.address")); 54 } 55 push_tag(&mut tags, TAG_A, address); 56 } 57 if let Some(items) = document.tags.as_ref() { 58 for item in items.iter().filter(|v| !v.trim().is_empty()) { 59 push_tag(&mut tags, TAG_T, item); 60 } 61 } 62 Ok(tags) 63 } 64 65 #[cfg(feature = "serde_json")] 66 pub fn to_wire_parts(document: &RadrootsDocument) -> Result<WireEventParts, EventEncodeError> { 67 to_wire_parts_with_kind(document, KIND_DOCUMENT) 68 } 69 70 #[cfg(feature = "serde_json")] 71 pub fn to_wire_parts_with_kind( 72 document: &RadrootsDocument, 73 kind: u32, 74 ) -> Result<WireEventParts, EventEncodeError> { 75 if kind != KIND_DOCUMENT { 76 return Err(EventEncodeError::InvalidKind(kind)); 77 } 78 let tags = document_build_tags(document)?; 79 let content = serde_json::to_string(document).map_err(|_| EventEncodeError::Json)?; 80 Ok(WireEventParts { 81 kind, 82 content, 83 tags, 84 }) 85 } 86 87 #[cfg(test)] 88 mod tests { 89 use super::*; 90 use crate::test_fixtures::FIXTURE_ALICE_PUBLIC_KEY_HEX; 91 use radroots_events::document::RadrootsDocumentSubject; 92 93 fn sample_document_address() -> String { 94 format!("30340:{FIXTURE_ALICE_PUBLIC_KEY_HEX}:AAAAAAAAAAAAAAAAAAAAAA") 95 } 96 97 fn sample_document() -> RadrootsDocument { 98 RadrootsDocument { 99 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 100 doc_type: "charter".to_string(), 101 title: "Charter".to_string(), 102 version: "1.0.0".to_string(), 103 summary: None, 104 effective_at: None, 105 body_markdown: None, 106 subject: RadrootsDocumentSubject { 107 pubkey: FIXTURE_ALICE_PUBLIC_KEY_HEX.to_string(), 108 address: Some(sample_document_address()), 109 }, 110 tags: None, 111 } 112 } 113 114 #[test] 115 fn document_build_tags_includes_subject_address_when_present() { 116 let tags = document_build_tags(&sample_document()).expect("document tags"); 117 assert!( 118 tags.iter() 119 .any(|tag| tag.first().map(|v| v.as_str()) == Some("a")) 120 ); 121 } 122 123 #[test] 124 fn document_build_tags_omits_subject_address_when_absent() { 125 let mut document = sample_document(); 126 document.subject.address = None; 127 let tags = document_build_tags(&document).expect("document tags"); 128 assert!( 129 !tags 130 .iter() 131 .any(|tag| tag.first().map(|v| v.as_str()) == Some("a")) 132 ); 133 } 134 135 #[test] 136 fn document_build_tags_rejects_invalid_d_tag() { 137 let mut document = sample_document(); 138 document.d_tag = "invalid".to_string(); 139 let err = document_build_tags(&document).expect_err("expected invalid d_tag"); 140 assert!(matches!(err, EventEncodeError::InvalidField("d_tag"))); 141 } 142 }