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