lib

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

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 }