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 }