commit c84fa324d929f8becb044766a1faf8b006cb181e
parent e1e9e41cd85d3b9329a5947bdeb1c999bb317b79
Author: triesap <tyson@radroots.org>
Date: Thu, 19 Feb 2026 16:35:30 +0000
nostr-runtime: add subscription spec convenience builders
- add high-level constructors for text-note and kind-based subscription specs
- support author scoping and policy switching in fluent subscription spec builders
- preserve default stream timeout and reconnect settings across helper constructors
- add unit tests for subscription spec builder behavior and field updates
Diffstat:
1 file changed, 106 insertions(+), 1 deletion(-)
diff --git a/nostr-runtime/src/types.rs b/nostr-runtime/src/types.rs
@@ -1,6 +1,9 @@
use alloc::string::String;
#[cfg(feature = "nostr-client")]
-use radroots_nostr::prelude::RadrootsNostrFilter;
+use radroots_nostr::prelude::{
+ RadrootsNostrFilter, RadrootsNostrPublicKey, RadrootsNostrTimestamp, radroots_nostr_kind,
+ radroots_nostr_post_events_filter,
+};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RadrootsNostrSubscriptionPolicy {
@@ -44,6 +47,43 @@ impl RadrootsNostrSubscriptionSpec {
}
}
+ pub fn with_policy(mut self, policy: RadrootsNostrSubscriptionPolicy) -> Self {
+ self.policy = policy;
+ self
+ }
+
+ #[cfg(feature = "nostr-client")]
+ pub fn text_notes(
+ limit: Option<u16>,
+ since_unix: Option<u64>,
+ policy: RadrootsNostrSubscriptionPolicy,
+ ) -> Self {
+ Self::streaming(radroots_nostr_post_events_filter(limit, since_unix)).with_policy(policy)
+ }
+
+ #[cfg(feature = "nostr-client")]
+ pub fn by_kind(
+ kind: u16,
+ limit: Option<u16>,
+ since_unix: Option<u64>,
+ policy: RadrootsNostrSubscriptionPolicy,
+ ) -> Self {
+ let mut filter = RadrootsNostrFilter::new().kind(radroots_nostr_kind(kind));
+ if let Some(limit) = limit {
+ filter = filter.limit(limit.into());
+ }
+ if let Some(since) = since_unix {
+ filter = filter.since(RadrootsNostrTimestamp::from(since));
+ }
+ Self::streaming(filter).with_policy(policy)
+ }
+
+ #[cfg(feature = "nostr-client")]
+ pub fn by_author(mut self, author: RadrootsNostrPublicKey) -> Self {
+ self.filter = self.filter.author(author);
+ self
+ }
+
pub fn named(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
@@ -60,6 +100,71 @@ impl RadrootsNostrSubscriptionSpec {
}
}
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use radroots_nostr::prelude::RadrootsNostrKeys;
+
+ #[test]
+ fn text_notes_constructor_sets_defaults() {
+ let spec = RadrootsNostrSubscriptionSpec::text_notes(
+ Some(5),
+ Some(10),
+ RadrootsNostrSubscriptionPolicy::Streaming,
+ );
+ assert!(matches!(
+ spec.policy,
+ RadrootsNostrSubscriptionPolicy::Streaming
+ ));
+ assert_eq!(
+ spec.stream_timeout_secs,
+ RadrootsNostrSubscriptionSpec::DEFAULT_STREAM_TIMEOUT_SECS
+ );
+ assert_eq!(
+ spec.reconnect_delay_millis,
+ RadrootsNostrSubscriptionSpec::DEFAULT_RECONNECT_DELAY_MILLIS
+ );
+ }
+
+ #[test]
+ fn by_kind_constructor_respects_policy() {
+ let spec = RadrootsNostrSubscriptionSpec::by_kind(
+ 30023,
+ None,
+ None,
+ RadrootsNostrSubscriptionPolicy::OneShotOnEose,
+ );
+ assert!(matches!(
+ spec.policy,
+ RadrootsNostrSubscriptionPolicy::OneShotOnEose
+ ));
+ }
+
+ #[test]
+ fn builder_methods_update_spec_fields() {
+ let keys = RadrootsNostrKeys::generate();
+ let author = keys.public_key();
+ let spec = RadrootsNostrSubscriptionSpec::text_notes(
+ None,
+ None,
+ RadrootsNostrSubscriptionPolicy::Streaming,
+ )
+ .by_author(author)
+ .named("posts")
+ .stream_timeout_secs(12)
+ .reconnect_delay_millis(99)
+ .with_policy(RadrootsNostrSubscriptionPolicy::OneShotOnEose);
+
+ assert_eq!(spec.name.as_deref(), Some("posts"));
+ assert_eq!(spec.stream_timeout_secs, 12);
+ assert_eq!(spec.reconnect_delay_millis, 99);
+ assert!(matches!(
+ spec.policy,
+ RadrootsNostrSubscriptionPolicy::OneShotOnEose
+ ));
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RadrootsNostrSubscriptionHandle {
pub id: String,