types.rs (7967B)
1 use alloc::string::String; 2 #[cfg(feature = "nostr-client")] 3 use radroots_nostr::prelude::{ 4 RadrootsNostrFilter, RadrootsNostrPublicKey, RadrootsNostrTimestamp, radroots_nostr_kind, 5 radroots_nostr_post_events_filter, 6 }; 7 8 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 pub enum RadrootsNostrSubscriptionPolicy { 10 Streaming, 11 OneShotOnEose, 12 } 13 14 #[derive(Debug, Clone)] 15 pub struct RadrootsNostrSubscriptionSpec { 16 pub name: Option<String>, 17 #[cfg(feature = "nostr-client")] 18 pub filter: RadrootsNostrFilter, 19 pub policy: RadrootsNostrSubscriptionPolicy, 20 pub stream_timeout_secs: u64, 21 pub reconnect_delay_millis: u64, 22 } 23 24 impl RadrootsNostrSubscriptionSpec { 25 pub const DEFAULT_STREAM_TIMEOUT_SECS: u64 = 30; 26 pub const DEFAULT_RECONNECT_DELAY_MILLIS: u64 = 2_000; 27 28 #[cfg(feature = "nostr-client")] 29 pub fn streaming(filter: RadrootsNostrFilter) -> Self { 30 Self { 31 name: None, 32 filter, 33 policy: RadrootsNostrSubscriptionPolicy::Streaming, 34 stream_timeout_secs: Self::DEFAULT_STREAM_TIMEOUT_SECS, 35 reconnect_delay_millis: Self::DEFAULT_RECONNECT_DELAY_MILLIS, 36 } 37 } 38 39 #[cfg(feature = "nostr-client")] 40 pub fn one_shot(filter: RadrootsNostrFilter) -> Self { 41 Self { 42 name: None, 43 filter, 44 policy: RadrootsNostrSubscriptionPolicy::OneShotOnEose, 45 stream_timeout_secs: Self::DEFAULT_STREAM_TIMEOUT_SECS, 46 reconnect_delay_millis: Self::DEFAULT_RECONNECT_DELAY_MILLIS, 47 } 48 } 49 50 pub fn with_policy(mut self, policy: RadrootsNostrSubscriptionPolicy) -> Self { 51 self.policy = policy; 52 self 53 } 54 55 #[cfg(feature = "nostr-client")] 56 pub fn text_notes( 57 limit: Option<u16>, 58 since_unix: Option<u64>, 59 policy: RadrootsNostrSubscriptionPolicy, 60 ) -> Self { 61 Self::streaming(radroots_nostr_post_events_filter(limit, since_unix)).with_policy(policy) 62 } 63 64 #[cfg(feature = "nostr-client")] 65 pub fn by_kind( 66 kind: u16, 67 limit: Option<u16>, 68 since_unix: Option<u64>, 69 policy: RadrootsNostrSubscriptionPolicy, 70 ) -> Self { 71 let mut filter = RadrootsNostrFilter::new().kind(radroots_nostr_kind(kind)); 72 if let Some(limit) = limit { 73 filter = filter.limit(limit.into()); 74 } 75 if let Some(since) = since_unix { 76 filter = filter.since(RadrootsNostrTimestamp::from(since)); 77 } 78 Self::streaming(filter).with_policy(policy) 79 } 80 81 #[cfg(feature = "nostr-client")] 82 pub fn by_author(mut self, author: RadrootsNostrPublicKey) -> Self { 83 self.filter = self.filter.author(author); 84 self 85 } 86 87 pub fn named(mut self, name: impl Into<String>) -> Self { 88 self.name = Some(name.into()); 89 self 90 } 91 92 pub fn stream_timeout_secs(mut self, value: u64) -> Self { 93 self.stream_timeout_secs = value; 94 self 95 } 96 97 pub fn reconnect_delay_millis(mut self, value: u64) -> Self { 98 self.reconnect_delay_millis = value; 99 self 100 } 101 } 102 103 #[cfg(test)] 104 mod tests { 105 use super::*; 106 #[cfg(feature = "nostr-client")] 107 use radroots_nostr::prelude::RadrootsNostrKeys; 108 109 fn base_spec() -> RadrootsNostrSubscriptionSpec { 110 RadrootsNostrSubscriptionSpec { 111 name: None, 112 #[cfg(feature = "nostr-client")] 113 filter: radroots_nostr::prelude::RadrootsNostrFilter::new(), 114 policy: RadrootsNostrSubscriptionPolicy::Streaming, 115 stream_timeout_secs: RadrootsNostrSubscriptionSpec::DEFAULT_STREAM_TIMEOUT_SECS, 116 reconnect_delay_millis: RadrootsNostrSubscriptionSpec::DEFAULT_RECONNECT_DELAY_MILLIS, 117 } 118 } 119 120 #[cfg(feature = "nostr-client")] 121 #[test] 122 fn text_notes_constructor_sets_defaults() { 123 let spec = RadrootsNostrSubscriptionSpec::text_notes( 124 Some(5), 125 Some(10), 126 RadrootsNostrSubscriptionPolicy::Streaming, 127 ); 128 assert!(matches!( 129 spec.policy, 130 RadrootsNostrSubscriptionPolicy::Streaming 131 )); 132 assert_eq!( 133 spec.stream_timeout_secs, 134 RadrootsNostrSubscriptionSpec::DEFAULT_STREAM_TIMEOUT_SECS 135 ); 136 assert_eq!( 137 spec.reconnect_delay_millis, 138 RadrootsNostrSubscriptionSpec::DEFAULT_RECONNECT_DELAY_MILLIS 139 ); 140 } 141 142 #[cfg(feature = "nostr-client")] 143 #[test] 144 fn by_kind_constructor_respects_policy() { 145 let spec = RadrootsNostrSubscriptionSpec::by_kind( 146 30023, 147 None, 148 None, 149 RadrootsNostrSubscriptionPolicy::OneShotOnEose, 150 ); 151 assert!(matches!( 152 spec.policy, 153 RadrootsNostrSubscriptionPolicy::OneShotOnEose 154 )); 155 } 156 157 #[cfg(feature = "nostr-client")] 158 #[test] 159 fn builder_methods_update_spec_fields() { 160 let keys = RadrootsNostrKeys::generate(); 161 let author = keys.public_key(); 162 let spec = RadrootsNostrSubscriptionSpec::text_notes( 163 None, 164 None, 165 RadrootsNostrSubscriptionPolicy::Streaming, 166 ) 167 .by_author(author) 168 .named("posts") 169 .stream_timeout_secs(12) 170 .reconnect_delay_millis(99) 171 .with_policy(RadrootsNostrSubscriptionPolicy::OneShotOnEose); 172 173 assert_eq!(spec.name.as_deref(), Some("posts")); 174 assert_eq!(spec.stream_timeout_secs, 12); 175 assert_eq!(spec.reconnect_delay_millis, 99); 176 assert_eq!(spec.policy, RadrootsNostrSubscriptionPolicy::OneShotOnEose); 177 } 178 179 #[test] 180 fn builder_methods_update_common_fields_without_client_feature() { 181 let spec = base_spec() 182 .named("posts") 183 .stream_timeout_secs(12) 184 .reconnect_delay_millis(99) 185 .with_policy(RadrootsNostrSubscriptionPolicy::OneShotOnEose); 186 187 assert_eq!(spec.name.as_deref(), Some("posts")); 188 assert_eq!(spec.stream_timeout_secs, 12); 189 assert_eq!(spec.reconnect_delay_millis, 99); 190 assert_eq!(spec.policy, RadrootsNostrSubscriptionPolicy::OneShotOnEose); 191 } 192 193 #[test] 194 fn connection_snapshot_default_is_red() { 195 let snapshot = RadrootsNostrConnectionSnapshot::default(); 196 assert_eq!(snapshot.light, RadrootsNostrTrafficLight::Red); 197 assert_eq!(snapshot.connected, 0); 198 assert_eq!(snapshot.connecting, 0); 199 assert!(snapshot.last_error.is_none()); 200 } 201 202 #[test] 203 fn branch_probe_covers_true_and_false_paths() { 204 let mut total = 0; 205 for flag in [true, false] { 206 if flag { 207 total += 1; 208 } else { 209 total += 2; 210 } 211 } 212 assert_eq!(total, 3); 213 } 214 } 215 216 #[derive(Debug, Clone, PartialEq, Eq, Hash)] 217 pub struct RadrootsNostrSubscriptionHandle { 218 pub id: String, 219 pub name: Option<String>, 220 } 221 222 #[derive(Debug, Clone)] 223 pub enum RadrootsNostrRuntimeEvent { 224 RuntimeStarted, 225 RuntimeStopped, 226 SubscriptionOpened { 227 id: String, 228 }, 229 SubscriptionClosed { 230 id: String, 231 }, 232 Note { 233 subscription_id: String, 234 id: String, 235 author: String, 236 kind: u16, 237 relay: Option<String>, 238 }, 239 Notice { 240 relay: String, 241 message: String, 242 }, 243 Error { 244 message: String, 245 }, 246 } 247 248 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 249 pub enum RadrootsNostrTrafficLight { 250 Red, 251 Yellow, 252 Green, 253 } 254 255 #[derive(Debug, Clone)] 256 pub struct RadrootsNostrConnectionSnapshot { 257 pub light: RadrootsNostrTrafficLight, 258 pub connected: usize, 259 pub connecting: usize, 260 pub last_error: Option<String>, 261 } 262 263 impl Default for RadrootsNostrConnectionSnapshot { 264 fn default() -> Self { 265 Self { 266 light: RadrootsNostrTrafficLight::Red, 267 connected: 0, 268 connecting: 0, 269 last_error: None, 270 } 271 } 272 }