lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

decode.rs (5905B)


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