app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

config.rs (5070B)


      1 use harvestcircle_domain::{SafeError, SafeErrorCode, SafeMessage};
      2 use radroots_transport_nostr::{RelayAccess, RelayEndpoint, RelayUrlPolicy};
      3 
      4 use crate::RelayConfiguration;
      5 
      6 #[derive(Clone, Debug, Eq, PartialEq)]
      7 pub struct RelayEndpointInput {
      8     url: String,
      9     destination: RelayUrlPolicy,
     10     read: bool,
     11     write: bool,
     12 }
     13 
     14 impl RelayEndpointInput {
     15     #[must_use]
     16     pub fn new(url: String, destination: RelayUrlPolicy, read: bool, write: bool) -> Self {
     17         Self {
     18             url,
     19             destination,
     20             read,
     21             write,
     22         }
     23     }
     24 }
     25 
     26 /// Validates explicitly classified relay endpoints supplied by a platform host.
     27 ///
     28 /// # Errors
     29 ///
     30 /// Returns a safe configuration error when an entry is invalid or no relay was
     31 /// explicitly supplied.
     32 pub fn relay_configuration_from_endpoints(
     33     values: &[RelayEndpointInput],
     34 ) -> Result<RelayConfiguration, SafeError> {
     35     if values.is_empty() || values.len() > crate::MAX_CONFIGURED_RELAYS {
     36         return Err(invalid_configuration());
     37     }
     38     let mut endpoints = Vec::with_capacity(values.len());
     39     for value in values {
     40         let access = match (value.read, value.write) {
     41             (true, false) => RelayAccess::ReadOnly,
     42             (true, true) => RelayAccess::ReadWrite,
     43             (false, _) => return Err(invalid_configuration()),
     44         };
     45         let endpoint = RelayEndpoint::new(&value.url, value.destination, access)
     46             .map_err(|_| invalid_configuration())?;
     47         if endpoints
     48             .iter()
     49             .any(|existing: &RelayEndpoint| existing.url() == endpoint.url())
     50         {
     51             return Err(invalid_configuration());
     52         }
     53         endpoints.push(endpoint);
     54     }
     55     RelayConfiguration::new(endpoints)
     56 }
     57 
     58 const fn invalid_configuration() -> SafeError {
     59     SafeError::new(
     60         SafeErrorCode::InvalidRelayConfiguration,
     61         SafeMessage::new("The Nostr relay configuration is invalid."),
     62     )
     63 }
     64 
     65 #[cfg(test)]
     66 mod tests {
     67     use harvestcircle_domain::SafeErrorCode;
     68     use radroots_transport_nostr::RelayUrlPolicy;
     69 
     70     use super::{RelayEndpointInput, relay_configuration_from_endpoints};
     71 
     72     #[test]
     73     fn relay_config_requires_explicit_input_and_one_governed_profile() {
     74         let error = relay_configuration_from_endpoints(&[]).expect_err("input required");
     75         assert_eq!(error.code(), SafeErrorCode::InvalidRelayConfiguration);
     76 
     77         let development = relay_configuration_from_endpoints(&[
     78             RelayEndpointInput::new(
     79                 "ws://localhost:8080".to_owned(),
     80                 RelayUrlPolicy::Local,
     81                 true,
     82                 true,
     83             ),
     84             RelayEndpointInput::new(
     85                 "ws://127.0.0.1:8081".to_owned(),
     86                 RelayUrlPolicy::Local,
     87                 true,
     88                 true,
     89             ),
     90         ])
     91         .expect("explicit development relay");
     92         assert_eq!(
     93             development.relays()[0].url().as_str(),
     94             "ws://localhost:8080"
     95         );
     96         assert_eq!(development.relays()[1].policy(), RelayUrlPolicy::Local);
     97 
     98         let mixed = [
     99             RelayEndpointInput::new(
    100                 "ws://localhost:8080".to_owned(),
    101                 RelayUrlPolicy::Local,
    102                 true,
    103                 true,
    104             ),
    105             RelayEndpointInput::new(
    106                 "wss://relay.example".to_owned(),
    107                 RelayUrlPolicy::Public,
    108                 true,
    109                 true,
    110             ),
    111         ];
    112         assert!(relay_configuration_from_endpoints(&mixed).is_err());
    113     }
    114 
    115     #[test]
    116     fn relay_config_rejects_normalized_duplicates_and_capability_free_entries() {
    117         for values in [
    118             vec![
    119                 RelayEndpointInput::new(
    120                     "wss://relay.one".to_owned(),
    121                     RelayUrlPolicy::Public,
    122                     true,
    123                     false,
    124                 ),
    125                 RelayEndpointInput::new(
    126                     "wss://RELAY.one/".to_owned(),
    127                     RelayUrlPolicy::Public,
    128                     false,
    129                     true,
    130                 ),
    131             ],
    132             vec![RelayEndpointInput::new(
    133                 "wss://relay.one".to_owned(),
    134                 RelayUrlPolicy::Public,
    135                 false,
    136                 false,
    137             )],
    138         ] {
    139             assert!(relay_configuration_from_endpoints(&values).is_err());
    140         }
    141     }
    142 
    143     #[test]
    144     fn relay_config_rejects_any_invalid_comma_separated_entry() {
    145         let error = relay_configuration_from_endpoints(&[
    146             RelayEndpointInput::new(
    147                 "wss://relay.one".to_owned(),
    148                 RelayUrlPolicy::Public,
    149                 true,
    150                 true,
    151             ),
    152             RelayEndpointInput::new(
    153                 "https://not-a-relay.test".to_owned(),
    154                 RelayUrlPolicy::Public,
    155                 true,
    156                 true,
    157             ),
    158         ])
    159         .expect_err("invalid entry");
    160         assert_eq!(error.code(), SafeErrorCode::InvalidRelayConfiguration);
    161     }
    162 }