use std::collections::BTreeSet;
use std::future::IntoFuture;
use std::time::Duration;
use futures::StreamExt;
use nostr::event::Event;
use super::req_target::ReqTarget;
use super::stream_events::StreamEvents;
use crate::client::{Client, Error};
use crate::future::BoxedFuture;
use crate::relay::{DEFAULT_FETCH_EVENTS_LIMIT, ReqExitPolicy};
#[must_use = "Does nothing unless you await!"]
pub struct FetchEvents<'client, 'url> {
client: &'client Client,
target: ReqTarget<'url>,
timeout: Option<Duration>,
policy: ReqExitPolicy,
max_events: usize,
}
impl<'client, 'url> FetchEvents<'client, 'url> {
pub(crate) fn new(client: &'client Client, target: ReqTarget<'url>) -> Self {
Self {
client,
target,
timeout: None,
policy: ReqExitPolicy::ExitOnEOSE,
max_events: DEFAULT_FETCH_EVENTS_LIMIT,
}
}
#[inline]
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
#[inline]
pub fn policy(mut self, policy: ReqExitPolicy) -> Self {
self.policy = policy;
self
}
#[inline]
pub fn max_events(mut self, max: usize) -> Self {
self.max_events = max;
self
}
}
impl<'client, 'url> IntoFuture for FetchEvents<'client, 'url>
where
'url: 'client,
{
type Output = Result<BTreeSet<Event>, Error>;
type IntoFuture = BoxedFuture<'client, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut stream: StreamEvents<'client, 'url> =
self.client.stream_events(self.target).policy(self.policy);
if let Some(timeout) = self.timeout {
stream = stream.timeout(timeout);
}
let mut stream = stream.await?;
let mut events: BTreeSet<Event> = BTreeSet::new();
while let Some((url, result)) = stream.next().await {
match result {
Ok(event) => {
if events.len() >= self.max_events && !events.contains(&event) {
return Err(Error::limit_exceeded("too many fetched events"));
}
events.insert(event);
}
Err(e) => {
tracing::error!(url = %url, error = %e, "Failed to handle streamed event");
}
}
}
Ok(events)
})
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use nostr::event::{EventBuilder, FinalizeEvent, Kind};
use nostr::filter::Filter;
use nostr::key::Keys;
use crate::authenticator::SignerAuthenticator;
use crate::error::ErrorKind;
use crate::local_relay::LocalRelay;
use crate::test_utils::{
setup_client, setup_client_with_authenticator, setup_nip42_read_local_relay,
};
#[tokio::test]
async fn test_client_fetch_events_dont_resubscribes_after_auth_required_closed_without_authenticator()
{
let local = setup_nip42_read_local_relay().await;
let keys = Keys::generate();
let expected = EventBuilder::new(Kind::TextNote, "Test")
.finalize(&keys)
.unwrap();
local.add_event(expected.clone()).await.unwrap();
let client = setup_client(local.url().await).await;
let filter = Filter::new().kind(Kind::TextNote).limit(1);
let events = client
.fetch_events(filter)
.timeout(Duration::from_secs(5))
.await
.unwrap();
assert_eq!(events.len(), 0);
}
#[tokio::test]
async fn test_client_fetch_events_resubscribes_after_auth_required_closed() {
let local = setup_nip42_read_local_relay().await;
let keys = Keys::generate();
let expected = EventBuilder::new(Kind::TextNote, "Test")
.finalize(&keys)
.unwrap();
local.add_event(expected.clone()).await.unwrap();
let authenticator = SignerAuthenticator::new(keys);
let client = setup_client_with_authenticator(local.url().await, authenticator).await;
let filter = Filter::new().kind(Kind::TextNote).limit(1);
let events = client
.fetch_events(filter)
.timeout(Duration::from_secs(5))
.await
.unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events.first().map(|event| event.id), Some(expected.id));
}
#[tokio::test]
async fn test_client_fetch_events_enforces_buffer_limit() {
let local = LocalRelay::new();
local.run().await.unwrap();
let keys = Keys::generate();
for i in 0..5 {
let event = EventBuilder::new(Kind::TextNote, i.to_string())
.finalize(&keys)
.unwrap();
local.add_event(event).await.unwrap();
}
let client = setup_client(local.url().await).await;
let err = client
.fetch_events(Filter::new().kind(Kind::TextNote))
.max_events(3)
.timeout(Duration::from_secs(5))
.await
.unwrap_err();
assert_eq!(err.kind(), ErrorKind::LimitExceeded);
}
}