lib

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

application_handler.rs (4869B)


      1 //! Typed authoring helpers for NIP-89 application-handler announcements.
      2 
      3 #![forbid(unsafe_code)]
      4 
      5 extern crate alloc;
      6 
      7 use alloc::{string::String, vec::Vec};
      8 
      9 use crate::error::Error;
     10 use crate::events::build_event_unchecked;
     11 use crate::types::{GenericBuilder, RadrootsNostrMetadata};
     12 use radroots_event::envelope::kind::KIND_APPLICATION_HANDLER;
     13 
     14 #[derive(Debug, Clone)]
     15 pub struct ApplicationHandlerSpec {
     16     kinds: Vec<u32>,
     17     identifier: Option<String>,
     18     metadata: Option<RadrootsNostrMetadata>,
     19     extra_tags: Vec<Vec<String>>,
     20     relays: Vec<String>,
     21     nostrconnect_url: Option<String>,
     22 }
     23 
     24 impl ApplicationHandlerSpec {
     25     pub fn new(kinds: Vec<u32>) -> Self {
     26         Self {
     27             kinds,
     28             identifier: None,
     29             metadata: None,
     30             extra_tags: Vec::new(),
     31             relays: Vec::new(),
     32             nostrconnect_url: None,
     33         }
     34     }
     35 
     36     #[must_use]
     37     pub fn with_identifier(mut self, identifier: impl Into<String>) -> Self {
     38         self.identifier = Some(identifier.into());
     39         self
     40     }
     41 
     42     #[must_use]
     43     pub fn with_metadata(mut self, metadata: RadrootsNostrMetadata) -> Self {
     44         self.metadata = Some(metadata);
     45         self
     46     }
     47 
     48     #[must_use]
     49     pub fn with_extra_tags(mut self, extra_tags: Vec<Vec<String>>) -> Self {
     50         self.extra_tags = extra_tags;
     51         self
     52     }
     53 
     54     #[must_use]
     55     pub fn with_relays(mut self, relays: Vec<String>) -> Self {
     56         self.relays = relays;
     57         self
     58     }
     59 
     60     #[must_use]
     61     pub fn with_nostr_connect_url(mut self, url: impl Into<String>) -> Self {
     62         self.nostrconnect_url = Some(url.into());
     63         self
     64     }
     65 
     66     pub fn kinds(&self) -> &[u32] {
     67         &self.kinds
     68     }
     69 
     70     pub fn identifier(&self) -> Option<&str> {
     71         self.identifier.as_deref()
     72     }
     73 
     74     pub fn metadata(&self) -> Option<&RadrootsNostrMetadata> {
     75         self.metadata.as_ref()
     76     }
     77 
     78     pub fn extra_tags(&self) -> &[Vec<String>] {
     79         &self.extra_tags
     80     }
     81 
     82     pub fn relays(&self) -> &[String] {
     83         &self.relays
     84     }
     85 
     86     pub fn nostr_connect_url(&self) -> Option<&str> {
     87         self.nostrconnect_url.as_deref()
     88     }
     89 }
     90 
     91 pub fn build_application_handler_event(
     92     spec: &ApplicationHandlerSpec,
     93 ) -> Result<GenericBuilder, Error> {
     94     if spec.kinds.is_empty() {
     95         return Err(Error::FilterTagError(
     96             "application handler kinds are empty".to_string(),
     97         ));
     98     }
     99 
    100     let identifier = spec
    101         .identifier
    102         .clone()
    103         .unwrap_or_else(|| spec.kinds[0].to_string());
    104 
    105     let mut content = String::new();
    106     if let Some(md) = spec.metadata.as_ref()
    107         && metadata_has_fields(md)
    108     {
    109         content = serde_json::to_string(md).unwrap_or_default();
    110     }
    111 
    112     let mut tags = Vec::new();
    113     tags.push(vec!["d".to_string(), identifier]);
    114     for kind in &spec.kinds {
    115         tags.push(vec!["k".to_string(), kind.to_string()]);
    116     }
    117     for relay in &spec.relays {
    118         let relay = relay.trim();
    119         if relay.is_empty() {
    120             continue;
    121         }
    122         tags.push(vec!["relay".to_string(), relay.to_string()]);
    123     }
    124     if let Some(url) = spec.nostrconnect_url.as_ref() {
    125         let url = url.trim();
    126         if !url.is_empty() {
    127             tags.push(vec!["nostrconnect_url".to_string(), url.to_string()]);
    128         }
    129     }
    130     for tag in &spec.extra_tags {
    131         if tag.is_empty() {
    132             continue;
    133         }
    134         tags.push(tag.clone());
    135     }
    136 
    137     let builder = build_event_unchecked(KIND_APPLICATION_HANDLER, content, tags)?;
    138     Ok(GenericBuilder::from_unchecked(builder))
    139 }
    140 
    141 pub fn metadata_has_fields(md: &RadrootsNostrMetadata) -> bool {
    142     md.name.is_some()
    143         || md.display_name.is_some()
    144         || md.about.is_some()
    145         || md.website.is_some()
    146         || md.picture.is_some()
    147         || md.banner.is_some()
    148         || md.nip05.is_some()
    149         || md.lud06.is_some()
    150         || md.lud16.is_some()
    151         || !md.custom.is_empty()
    152 }
    153 
    154 #[cfg(test)]
    155 mod tests {
    156     use super::{ApplicationHandlerSpec, build_application_handler_event, metadata_has_fields};
    157     use crate::error::Error;
    158     use crate::types::RadrootsNostrMetadata;
    159 
    160     #[test]
    161     fn metadata_has_fields_false_when_empty() {
    162         assert!(!metadata_has_fields(&RadrootsNostrMetadata::default()));
    163     }
    164 
    165     #[test]
    166     fn metadata_has_fields_true_when_about_is_set() {
    167         let metadata = RadrootsNostrMetadata {
    168             about: Some("ready".to_string()),
    169             ..Default::default()
    170         };
    171         assert!(metadata_has_fields(&metadata));
    172     }
    173 
    174     #[test]
    175     fn application_handler_requires_at_least_one_kind() {
    176         assert!(matches!(
    177             build_application_handler_event(&ApplicationHandlerSpec::new(Vec::new())),
    178             Err(Error::FilterTagError(message)) if message == "application handler kinds are empty"
    179         ));
    180     }
    181 }