Skip to main content

umadb_dcb/
lib.rs

1//! API for Dynamic Consistency Boundaries (DCB) event store
2//!
3//! This module provides the core interfaces and data structures for working with
4//! an event store that supports dynamic consistency boundaries.
5
6use async_trait::async_trait;
7use futures_core::Stream;
8use futures_util::StreamExt;
9use std::iter::Iterator;
10use std::sync::Arc;
11use std::time::Duration;
12use thiserror::Error;
13use uuid::Uuid;
14
15/// A cloneable, thread-safe handle that can end an individual streaming
16/// response/subscription without needing mutable (or any) access to the
17/// response object itself.
18///
19/// This is important because the Python bindings guard the response behind a
20/// `Mutex` that is held while blocking on the next batch of events. Calling
21/// `stop()` on the response directly would require acquiring that same `Mutex`,
22/// which is impossible while a read/next call is blocked. A `StreamCancelHandle` can be
23/// obtained up front and used from another thread to signal the stream to end.
24#[derive(Clone)]
25pub struct StreamCancelHandle(Arc<dyn Fn() + Send + Sync>);
26
27impl StreamCancelHandle {
28    /// Creates a new `StreamCancelHandle` from the given closure.
29    pub fn new<F>(f: F) -> Self
30    where
31        F: Fn() + Send + Sync + 'static,
32    {
33        StreamCancelHandle(Arc::new(f))
34    }
35
36    /// Signals the associated stream.
37    pub fn cancel(&self) {
38        (self.0)()
39    }
40}
41
42impl std::fmt::Debug for StreamCancelHandle {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.write_str("StreamCancelHandle")
45    }
46}
47
48/// Non-async Rust interface for recording and retrieving events
49pub trait DcbEventStoreSync {
50    /// Reads events from the store based on the provided query and constraints
51    ///
52    /// Returns a `DcbReadResponseSync` that provides an iterator over all events,
53    /// unless 'from' is given then only those with position greater than 'after',
54    /// and unless any query items are given, then only those that match at least one
55    /// query item. An event matches a query item if its type is in the item types or
56    /// there are no item types, and if all the item tags are in the event tags.
57    fn read(
58        &self,
59        query: Option<DcbQuery>,
60        start: Option<u64>,
61        backwards: bool,
62        limit: Option<u32>,
63    ) -> DcbResult<Box<dyn DcbReadResponseSync + Send + 'static>>;
64
65    /// Reads events from the store and returns them as a tuple of `(Vec<DcbSequencedEvent>, Option<u64>)`
66    fn read_with_head(
67        &self,
68        query: Option<DcbQuery>,
69        start: Option<u64>,
70        backwards: bool,
71        limit: Option<u32>,
72    ) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)> {
73        let mut response = self.read(query, start, backwards, limit)?;
74        response.collect_with_head()
75    }
76
77    /// Returns the current head position of the event store, or None if empty
78    ///
79    /// Returns the value of `last_committed_position`, or `None` if `last_committed_position` is zero
80    fn head(&self) -> DcbResult<Option<u64>>;
81
82    /// Returns the greatest recorded upstream position for a tracking source, if any
83    fn get_tracking_info(&self, source: &str) -> DcbResult<Option<u64>>;
84
85    /// Appends given events to the event store, unless the condition fails
86    ///
87    /// Returns the position of the last appended event
88    fn append(
89        &self,
90        events: Vec<DcbEvent>,
91        condition: Option<DcbAppendCondition>,
92        tracking_info: Option<TrackingInfo>,
93    ) -> DcbResult<u64>;
94}
95
96/// Response from a read operation, providing an iterator over sequenced events
97pub trait DcbReadResponseSync: Iterator<Item = DcbResult<DcbSequencedEvent>> + Send {
98    /// Returns the current head position of the event store, or None if empty
99    fn head(&mut self) -> DcbResult<Option<u64>>;
100    /// Returns a vector of events with head
101    fn collect_with_head(&mut self) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)>;
102    /// Returns the next batch of events for this read. Implementations may buffer
103    /// events per underlying transport message ("batch"). If there are no more
104    /// events available, returns an empty Vec. The head() method should reflect
105    /// the latest known head as reported by the underlying store.
106    fn next_batch(&mut self) -> DcbResult<Vec<DcbSequencedEvent>>;
107
108    /// Ends this individual streaming response.
109    ///
110    /// After calling `stop()`, the iterator/`next_batch()` will stop yielding new
111    /// events (returning `None`/an empty `Vec`). Unlike the global stop signal,
112    /// this only affects this particular response. The default implementation is
113    /// a no-op for backends that do not support per-stream stopping.
114    fn cancel(&mut self) {}
115
116    /// Returns a cloneable [`StreamCancelHandle`] that can end this response from
117    /// another thread without requiring access to the response itself.
118    ///
119    /// Returns `None` for backends that do not support per-stream stopping.
120    fn cancel_handle(&self) -> Option<StreamCancelHandle> {
121        None
122    }
123
124    fn next_timeout(&mut self, _timeout: Duration) -> Option<DcbResult<DcbSequencedEvent>> {
125        // Fallback default behaviour: just call normal blocking next()
126        self.next()
127    }
128}
129
130/// Response from a subscribe operation, providing an iterator over sequenced events
131pub trait DcbSubscriptionSync: Iterator<Item = DcbResult<DcbSequencedEvent>> + Send {
132    /// Returns the next batch of events for this read. Implementations may buffer
133    /// events per underlying transport message ("batch"). If there are no more
134    /// events available, returns an empty Vec.
135    fn next_batch(&mut self) -> DcbResult<Vec<DcbSequencedEvent>>;
136    fn next_batch_timeout(&mut self, timeout: Duration) -> DcbResult<Vec<DcbSequencedEvent>>;
137
138    /// Ends this individual streaming subscription.
139    ///
140    /// After calling `cancel()`, the iterator/`next_batch()` will return
141    /// `Err(DcbError:CancelledByUser)`. Unlike the global cancel signal,
142    /// this only affects this particular subscription. The default implementation
143    /// is a no-op for backends that do not support per-stream cancel.
144    fn cancel(&mut self);
145
146    /// Default implementation that falls back to blocking if not overridden.
147    fn next_timeout(&mut self, _timeout: Duration) -> Option<DcbResult<DcbSequencedEvent>> {
148        // Fallback default behaviour: just call normal blocking next()
149        self.next()
150    }
151}
152
153/// Async Rust interface for recording and retrieving events
154#[async_trait]
155pub trait DcbEventStoreAsync: Send + Sync {
156    /// Reads events from the store based on the provided query and constraints
157    ///
158    /// Returns a `DcbReadResponseSync` that provides an iterator over all events,
159    /// unless 'after' is given then only those with position greater than 'after',
160    /// and unless any query items are given, then only those that match at least one
161    /// query item. An event matches a query item if its type is in the item types or
162    /// there are no item types, and if all the item tags are in the event tags.
163    async fn read<'a>(
164        &'a self,
165        query: Option<DcbQuery>,
166        start: Option<u64>,
167        backwards: bool,
168        limit: Option<u32>,
169    ) -> DcbResult<Box<dyn DcbReadResponseAsync + Send + 'static>>;
170
171    /// Reads events from the store and returns them as a tuple of `(Vec<DcbSequencedEvent>, Option<u64>)`
172    async fn read_with_head<'a>(
173        &'a self,
174        query: Option<DcbQuery>,
175        after: Option<u64>,
176        backwards: bool,
177        limit: Option<u32>,
178    ) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)> {
179        let mut response = self.read(query, after, backwards, limit).await?;
180        response.collect_with_head().await
181    }
182
183    /// Returns the current head position of the event store, or None if empty
184    ///
185    /// Returns the value of last_committed_position, or None if last_committed_position is zero
186    async fn head(&self) -> DcbResult<Option<u64>>;
187
188    /// Returns the greatest recorded upstream position for a tracking source, if any
189    async fn get_tracking_info(&self, source: &str) -> DcbResult<Option<u64>>;
190
191    /// Appends given events to the event store, unless the condition fails
192    ///
193    /// Returns the position of the last appended event
194    async fn append(
195        &self,
196        events: Vec<DcbEvent>,
197        condition: Option<DcbAppendCondition>,
198        tracking_info: Option<TrackingInfo>,
199    ) -> DcbResult<u64>;
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum ShutdownStatus {
204    NotStopped,
205    StoppedGracefully,
206    CancelledByUser,
207}
208
209/// Asynchronous response from a read operation, providing a stream of sequenced events
210#[async_trait]
211pub trait DcbReadResponseAsync: Stream<Item = DcbResult<DcbSequencedEvent>> + Send + Unpin {
212    async fn head(&mut self) -> DcbResult<Option<u64>>;
213
214    async fn collect_with_head(&mut self) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)> {
215        let mut events = Vec::new();
216        while let Some(result) = self.next().await {
217            events.push(result?); // propagate error from stream
218        }
219
220        let head = self.head().await?;
221        Ok((events, head))
222    }
223
224    async fn next_batch(&mut self) -> DcbResult<Vec<DcbSequencedEvent>>;
225    async fn next_batch_timeout(&mut self, timeout: Duration) -> DcbResult<Vec<DcbSequencedEvent>>;
226
227    /// Ends this individual streaming response.
228    ///
229    /// After calling `stop()`, the stream/`next_batch()` will stop yielding new
230    /// events. Unlike the global stop signal, this only affects this particular
231    /// response. The default implementation is a no-op for backends that do not
232    /// support per-stream stopping.
233    fn cancel(&mut self);
234
235    /// Returns a cloneable [`StreamCancelHandle`] that can end this response from
236    /// another thread without requiring access to the response itself.
237    fn cancel_handle(&self) -> Option<StreamCancelHandle> {
238        None
239    }
240
241    fn check_shutdown_status(&self) -> ShutdownStatus;
242}
243
244/// Asynchronous response from a subscribe operation, providing a stream of sequenced events
245#[async_trait]
246pub trait DcbSubscriptionAsync: Stream<Item = DcbResult<DcbSequencedEvent>> + Send + Unpin {
247    async fn next_batch(&mut self) -> DcbResult<Vec<DcbSequencedEvent>>;
248    async fn next_batch_timeout(&mut self, timeout: Duration) -> DcbResult<Vec<DcbSequencedEvent>>;
249    /// Ends this individual streaming subscription.
250    ///
251    /// After calling `stop()`, the stream/`next_batch()` will stop yielding new
252    /// events. Unlike the global stop signal, this only affects this particular
253    /// subscription. The default implementation is a no-op for backends that do
254    /// not support per-stream stopping.
255    fn cancel(&mut self) {}
256
257    /// Returns a cloneable [`StreamCancelHandle`] that can end this subscription from
258    /// another thread without requiring access to the subscription itself.
259    fn cancel_handle(&self) -> Option<StreamCancelHandle> {
260        None
261    }
262    fn check_shutdown_status(&self) -> ShutdownStatus;
263}
264
265/// Represents a query item for filtering events
266#[derive(Debug, Clone, Default)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
268pub struct DcbQueryItem {
269    /// Event types to match
270    pub types: Vec<String>,
271    /// Tags that must all be present in the event
272    pub tags: Vec<String>,
273}
274
275impl DcbQueryItem {
276    /// Creates a new query item
277    pub fn new() -> Self {
278        Self {
279            types: vec![],
280            tags: vec![],
281        }
282    }
283
284    /// Sets the types for this query item
285    pub fn types<I, S>(mut self, types: I) -> Self
286    where
287        I: IntoIterator<Item = S>,
288        S: Into<String>,
289    {
290        self.types = types.into_iter().map(|s| s.into()).collect();
291        self
292    }
293
294    /// Sets the tags for this query item
295    pub fn tags<I, S>(mut self, tags: I) -> Self
296    where
297        I: IntoIterator<Item = S>,
298        S: Into<String>,
299    {
300        self.tags = tags.into_iter().map(|s| s.into()).collect();
301        self
302    }
303}
304
305/// A query composed of multiple query items
306#[derive(Debug, Clone, Default)]
307#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
308pub struct DcbQuery {
309    /// List of query items, where events matching any item are included in results
310    pub items: Vec<DcbQueryItem>,
311}
312
313impl DcbQuery {
314    /// Creates a new empty query
315    pub fn new() -> Self {
316        Self { items: Vec::new() }
317    }
318
319    /// Creates a query with the specified items
320    pub fn with_items<I>(items: I) -> Self
321    where
322        I: IntoIterator<Item = DcbQueryItem>,
323    {
324        Self {
325            items: items.into_iter().collect(),
326        }
327    }
328
329    /// Adds a query item to this query
330    pub fn item(mut self, item: DcbQueryItem) -> Self {
331        self.items.push(item);
332        self
333    }
334
335    /// Adds multiple query items to this query
336    pub fn items<I>(mut self, items: I) -> Self
337    where
338        I: IntoIterator<Item = DcbQueryItem>,
339    {
340        self.items.extend(items);
341        self
342    }
343}
344
345/// Conditions that must be satisfied for an append operation to succeed
346#[derive(Debug, Clone, Default)]
347#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
348pub struct DcbAppendCondition {
349    /// Query that, if matching any events, will cause the append to fail
350    pub fail_if_events_match: DcbQuery,
351    /// Position after which to append; if None, append at the end
352    pub after: Option<u64>,
353}
354
355impl DcbAppendCondition {
356    /// Creates a new empty append condition
357    pub fn new(fail_if_events_match: DcbQuery) -> Self {
358        Self {
359            fail_if_events_match,
360            after: None,
361        }
362    }
363
364    pub fn after(mut self, after: Option<u64>) -> Self {
365        self.after = after;
366        self
367    }
368}
369
370/// Represents an event in the event store
371#[derive(Debug, Clone)]
372#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
373pub struct DcbEvent {
374    /// Type of the event
375    pub event_type: String,
376    /// Tags associated with the event
377    pub tags: Vec<String>,
378    /// Binary data associated with the event
379    pub data: Vec<u8>,
380    /// Unique event ID
381    pub uuid: Option<Uuid>,
382    /// Metadata for the event
383    pub metadata: Vec<(String, String)>,
384}
385
386impl Default for DcbEvent {
387    fn default() -> Self {
388        Self::new()
389    }
390}
391
392impl DcbEvent {
393    /// Creates a new event
394    pub fn new() -> Self {
395        Self {
396            event_type: "".to_string(),
397            data: Vec::new(),
398            tags: Vec::new(),
399            uuid: None,
400            metadata: Vec::new(),
401        }
402    }
403
404    /// Sets the type for this event
405    pub fn event_type<S: Into<String>>(mut self, event_type: S) -> Self {
406        self.event_type = event_type.into();
407        self
408    }
409
410    /// Sets the data for this event
411    pub fn data<D: Into<Vec<u8>>>(mut self, data: D) -> Self {
412        self.data = data.into();
413        self
414    }
415
416    /// Sets the tags for this event
417    pub fn tags<I, S>(mut self, tags: I) -> Self
418    where
419        I: IntoIterator<Item = S>,
420        S: Into<String>,
421    {
422        self.tags = tags.into_iter().map(|s| s.into()).collect();
423        self
424    }
425
426    /// Sets the UUID for this event
427    pub fn uuid(mut self, uuid: Uuid) -> Self {
428        self.uuid = Some(uuid);
429        self
430    }
431
432    /// Sets the metadata for this event, replacing any existing entries
433    pub fn metadata<I, K, V>(mut self, metadata: I) -> Self
434    where
435        I: IntoIterator<Item = (K, V)>,
436        K: Into<String>,
437        V: Into<String>,
438    {
439        self.metadata = metadata
440            .into_iter()
441            .map(|(k, v)| (k.into(), v.into()))
442            .collect();
443        self
444    }
445
446    /// Inserts a single metadata entry, keeping any existing entries
447    pub fn metadata_entry<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
448        let key = key.into();
449        let value = value.into();
450
451        if let Some((_, existing_value)) = self
452            .metadata
453            .iter_mut()
454            .find(|(existing_key, _)| *existing_key == key)
455        {
456            *existing_value = value;
457        } else {
458            self.metadata.push((key, value));
459        }
460
461        self
462    }
463}
464
465#[derive(Debug, Clone)]
466#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
467pub struct TrackingInfo {
468    pub source: String,
469    pub position: u64,
470}
471
472/// An event with its position in the event sequence
473#[derive(Debug, Clone)]
474#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
475pub struct DcbSequencedEvent {
476    /// Position of the event in the sequence
477    pub position: u64,
478    /// The event
479    pub event: DcbEvent,
480}
481
482// Error types
483#[derive(Error, Debug)]
484#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
485pub enum DcbError {
486    // Generic/system errors
487    #[error("io error: {0}")]
488    #[cfg_attr(feature = "serde", serde(with = "serde_io_error"))]
489    Io(#[from] std::io::Error),
490
491    // DCB domain errors
492    #[error("integrity error: condition failed: {0}")]
493    IntegrityError(String),
494    #[error("corruption detected: {0}")]
495    Corruption(String),
496    /// Invalid input argument provided by the caller
497    #[error("invalid argument: {0}")]
498    InvalidArgument(String),
499
500    // Storage errors (unified into DCBError)
501    #[error("initialization error: {0}")]
502    InitializationError(String),
503    #[error("page not found: {0}")]
504    PageNotFound(u64),
505    #[error("dirty page not found: {0}")]
506    DirtyPageNotFound(u64),
507    #[error("root ID mismatched: old {0} new {1}")]
508    RootIDMismatch(u64, u64),
509    #[error("database corrupted: {0}")]
510    DatabaseCorrupted(String),
511    #[error("internal error: {0}")]
512    InternalError(String),
513    #[error("serialization error: {0}")]
514    SerializationError(String),
515    #[error("deserialization error: {0}")]
516    DeserializationError(String),
517    #[error("page already freed: {0}")]
518    PageAlreadyFreed(u64),
519    #[error("page already dirty: {0}")]
520    PageAlreadyDirty(u64),
521    #[error("transport error: {0}")]
522    TransportError(String),
523    #[error("cancelled by user")]
524    CancelledByUser(),
525    #[error("timeout")]
526    Timeout(),
527
528    // Authentication error
529    #[error("authentication error: {0}")]
530    AuthenticationError(String),
531}
532
533pub type DcbResult<T> = Result<T, DcbError>;
534
535#[cfg(feature = "serde")]
536mod serde_io_error {
537    use std::{borrow::Cow, io};
538
539    use serde::{Deserialize, Serialize};
540
541    #[derive(Serialize, Deserialize)]
542    struct IoError {
543        kind: Option<Cow<'static, str>>,
544        message: Option<String>,
545    }
546
547    pub fn serialize<S>(err: &io::Error, serializer: S) -> Result<S::Ok, S::Error>
548    where
549        S: serde::Serializer,
550    {
551        let kind = match err.kind() {
552            io::ErrorKind::NotFound => Some(Cow::Borrowed("NotFound")),
553            io::ErrorKind::PermissionDenied => Some(Cow::Borrowed("PermissionDenied")),
554            io::ErrorKind::ConnectionRefused => Some(Cow::Borrowed("ConnectionRefused")),
555            io::ErrorKind::ConnectionReset => Some(Cow::Borrowed("ConnectionReset")),
556            io::ErrorKind::HostUnreachable => Some(Cow::Borrowed("HostUnreachable")),
557            io::ErrorKind::NetworkUnreachable => Some(Cow::Borrowed("NetworkUnreachable")),
558            io::ErrorKind::ConnectionAborted => Some(Cow::Borrowed("ConnectionAborted")),
559            io::ErrorKind::NotConnected => Some(Cow::Borrowed("NotConnected")),
560            io::ErrorKind::AddrInUse => Some(Cow::Borrowed("AddrInUse")),
561            io::ErrorKind::AddrNotAvailable => Some(Cow::Borrowed("AddrNotAvailable")),
562            io::ErrorKind::NetworkDown => Some(Cow::Borrowed("NetworkDown")),
563            io::ErrorKind::BrokenPipe => Some(Cow::Borrowed("BrokenPipe")),
564            io::ErrorKind::AlreadyExists => Some(Cow::Borrowed("AlreadyExists")),
565            io::ErrorKind::WouldBlock => Some(Cow::Borrowed("WouldBlock")),
566            io::ErrorKind::NotADirectory => Some(Cow::Borrowed("NotADirectory")),
567            io::ErrorKind::IsADirectory => Some(Cow::Borrowed("IsADirectory")),
568            io::ErrorKind::DirectoryNotEmpty => Some(Cow::Borrowed("DirectoryNotEmpty")),
569            io::ErrorKind::ReadOnlyFilesystem => Some(Cow::Borrowed("ReadOnlyFilesystem")),
570            io::ErrorKind::StaleNetworkFileHandle => Some(Cow::Borrowed("StaleNetworkFileHandle")),
571            io::ErrorKind::InvalidInput => Some(Cow::Borrowed("InvalidInput")),
572            io::ErrorKind::InvalidData => Some(Cow::Borrowed("InvalidData")),
573            io::ErrorKind::TimedOut => Some(Cow::Borrowed("TimedOut")),
574            io::ErrorKind::WriteZero => Some(Cow::Borrowed("WriteZero")),
575            io::ErrorKind::StorageFull => Some(Cow::Borrowed("StorageFull")),
576            io::ErrorKind::NotSeekable => Some(Cow::Borrowed("NotSeekable")),
577            io::ErrorKind::QuotaExceeded => Some(Cow::Borrowed("QuotaExceeded")),
578            io::ErrorKind::FileTooLarge => Some(Cow::Borrowed("FileTooLarge")),
579            io::ErrorKind::ResourceBusy => Some(Cow::Borrowed("ResourceBusy")),
580            io::ErrorKind::ExecutableFileBusy => Some(Cow::Borrowed("ExecutableFileBusy")),
581            io::ErrorKind::Deadlock => Some(Cow::Borrowed("Deadlock")),
582            io::ErrorKind::CrossesDevices => Some(Cow::Borrowed("CrossesDevices")),
583            io::ErrorKind::TooManyLinks => Some(Cow::Borrowed("TooManyLinks")),
584            io::ErrorKind::InvalidFilename => Some(Cow::Borrowed("InvalidFilename")),
585            io::ErrorKind::ArgumentListTooLong => Some(Cow::Borrowed("ArgumentListTooLong")),
586            io::ErrorKind::Interrupted => Some(Cow::Borrowed("Interrupted")),
587            io::ErrorKind::Unsupported => Some(Cow::Borrowed("Unsupported")),
588            io::ErrorKind::UnexpectedEof => Some(Cow::Borrowed("UnexpectedEof")),
589            io::ErrorKind::OutOfMemory => Some(Cow::Borrowed("OutOfMemory")),
590            io::ErrorKind::Other => Some(Cow::Borrowed("Other")),
591            _ => None,
592        };
593
594        IoError {
595            kind,
596            message: err.get_ref().map(|err| err.to_string()),
597        }
598        .serialize(serializer)
599    }
600
601    pub fn deserialize<'de, D>(deserializer: D) -> Result<io::Error, D::Error>
602    where
603        D: serde::Deserializer<'de>,
604    {
605        let io_err: IoError = <IoError as Deserialize>::deserialize(deserializer)?;
606        let kind = match io_err.kind.as_deref() {
607            Some("NotFound") => io::ErrorKind::NotFound,
608            Some("PermissionDenied") => io::ErrorKind::PermissionDenied,
609            Some("ConnectionRefused") => io::ErrorKind::ConnectionRefused,
610            Some("ConnectionReset") => io::ErrorKind::ConnectionReset,
611            Some("HostUnreachable") => io::ErrorKind::HostUnreachable,
612            Some("NetworkUnreachable") => io::ErrorKind::NetworkUnreachable,
613            Some("ConnectionAborted") => io::ErrorKind::ConnectionAborted,
614            Some("NotConnected") => io::ErrorKind::NotConnected,
615            Some("AddrInUse") => io::ErrorKind::AddrInUse,
616            Some("AddrNotAvailable") => io::ErrorKind::AddrNotAvailable,
617            Some("NetworkDown") => io::ErrorKind::NetworkDown,
618            Some("BrokenPipe") => io::ErrorKind::BrokenPipe,
619            Some("AlreadyExists") => io::ErrorKind::AlreadyExists,
620            Some("WouldBlock") => io::ErrorKind::WouldBlock,
621            Some("NotADirectory") => io::ErrorKind::NotADirectory,
622            Some("IsADirectory") => io::ErrorKind::IsADirectory,
623            Some("DirectoryNotEmpty") => io::ErrorKind::DirectoryNotEmpty,
624            Some("ReadOnlyFilesystem") => io::ErrorKind::ReadOnlyFilesystem,
625            Some("StaleNetworkFileHandle") => io::ErrorKind::StaleNetworkFileHandle,
626            Some("InvalidInput") => io::ErrorKind::InvalidInput,
627            Some("InvalidData") => io::ErrorKind::InvalidData,
628            Some("TimedOut") => io::ErrorKind::TimedOut,
629            Some("WriteZero") => io::ErrorKind::WriteZero,
630            Some("StorageFull") => io::ErrorKind::StorageFull,
631            Some("NotSeekable") => io::ErrorKind::NotSeekable,
632            Some("QuotaExceeded") => io::ErrorKind::QuotaExceeded,
633            Some("FileTooLarge") => io::ErrorKind::FileTooLarge,
634            Some("ResourceBusy") => io::ErrorKind::ResourceBusy,
635            Some("ExecutableFileBusy") => io::ErrorKind::ExecutableFileBusy,
636            Some("Deadlock") => io::ErrorKind::Deadlock,
637            Some("CrossesDevices") => io::ErrorKind::CrossesDevices,
638            Some("TooManyLinks") => io::ErrorKind::TooManyLinks,
639            Some("InvalidFilename") => io::ErrorKind::InvalidFilename,
640            Some("ArgumentListTooLong") => io::ErrorKind::ArgumentListTooLong,
641            Some("Interrupted") => io::ErrorKind::Interrupted,
642            Some("Unsupported") => io::ErrorKind::Unsupported,
643            Some("UnexpectedEof") => io::ErrorKind::UnexpectedEof,
644            Some("OutOfMemory") => io::ErrorKind::OutOfMemory,
645            Some("Other") => io::ErrorKind::Other,
646            _ => io::ErrorKind::Other,
647        };
648
649        Ok(io::Error::new(
650            kind,
651            io_err
652                .message
653                .unwrap_or_else(|| "unknown error".to_string()),
654        ))
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661
662    // A simple implementation of DCBReadResponseSync for testing
663    struct TestReadResponse {
664        events: Vec<DcbSequencedEvent>,
665        current_index: usize,
666        head_position: Option<u64>,
667    }
668
669    impl TestReadResponse {
670        fn new(events: Vec<DcbSequencedEvent>, head_position: Option<u64>) -> Self {
671            Self {
672                events,
673                current_index: 0,
674                head_position,
675            }
676        }
677    }
678
679    impl Iterator for TestReadResponse {
680        type Item = DcbResult<DcbSequencedEvent>;
681
682        fn next(&mut self) -> Option<Self::Item> {
683            if self.current_index < self.events.len() {
684                let event = self.events[self.current_index].clone();
685                self.current_index += 1;
686                Some(Ok(event))
687            } else {
688                None
689            }
690        }
691    }
692
693    impl DcbReadResponseSync for TestReadResponse {
694        fn head(&mut self) -> DcbResult<Option<u64>> {
695            Ok(self.head_position)
696        }
697
698        fn collect_with_head(&mut self) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)> {
699            todo!()
700        }
701
702        fn next_batch(&mut self) -> DcbResult<Vec<DcbSequencedEvent>> {
703            let mut batch = Vec::new();
704            while let Some(result) = self.next() {
705                match result {
706                    Ok(event) => batch.push(event),
707                    Err(err) => {
708                        panic!("{}", err);
709                    }
710                }
711            }
712            Ok(batch)
713        }
714    }
715
716    #[test]
717    fn test_dcb_read_response() {
718        // Create some test events
719        let event1 = DcbEvent {
720            event_type: "test_event".to_string(),
721            data: vec![1, 2, 3],
722            tags: vec!["tag1".to_string(), "tag2".to_string()],
723            uuid: None,
724            metadata: Vec::new(),
725        };
726
727        let event2 = DcbEvent {
728            event_type: "another_event".to_string(),
729            data: vec![4, 5, 6],
730            tags: vec!["tag2".to_string(), "tag3".to_string()],
731            uuid: None,
732            metadata: Vec::new(),
733        };
734
735        let seq_event1 = DcbSequencedEvent {
736            event: event1,
737            position: 1,
738        };
739
740        let seq_event2 = DcbSequencedEvent {
741            event: event2,
742            position: 2,
743        };
744
745        // Create a test response
746        let mut response =
747            TestReadResponse::new(vec![seq_event1.clone(), seq_event2.clone()], Some(2));
748
749        // Test head position
750        assert_eq!(response.head().unwrap(), Some(2));
751
752        // Test iterator functionality
753        assert_eq!(response.next().unwrap().unwrap().position, 1);
754        assert_eq!(response.next().unwrap().unwrap().position, 2);
755        assert!(response.next().is_none());
756    }
757
758    #[test]
759    fn test_event_new() {
760        let event1 = DcbEvent::default()
761            .event_type("type1")
762            .data(b"data1")
763            .tags(["tagX"]);
764
765        // println!("Event created with builder API:");
766        // println!("  event_type: {}", event1.event_type);
767        // println!("  data: {:?}", event1.data);
768        // println!("  tags: {:?}", event1.tags);
769        // println!("  uuid: {:?}", event1.uuid);
770
771        // Verify the fields match expectations
772        assert_eq!(event1.event_type, "type1");
773        assert_eq!(event1.data, b"data1".to_vec());
774        assert_eq!(event1.tags, vec!["tagX".to_string()]);
775        assert_eq!(event1.uuid, None);
776
777        // Test with multiple tags
778        let event2 = DcbEvent::default()
779            .event_type("type2")
780            .data(b"data2")
781            .tags(["tag1", "tag2", "tag3"]);
782        assert_eq!(event2.tags.len(), 3);
783
784        // Test without data or tags
785        let event3 = DcbEvent::default().event_type("type3");
786        assert_eq!(event3.data.len(), 0);
787        assert_eq!(event3.tags.len(), 0);
788
789        // Test DCBQueryItem builder
790        let query_item = DcbQueryItem::new()
791            .types(["type1", "type2"])
792            .tags(["tagA", "tagB"]);
793        assert_eq!(query_item.types.len(), 2);
794        assert_eq!(query_item.tags.len(), 2);
795
796        // Test DCBQuery builder
797        let query = DcbQuery::new().item(query_item);
798        assert_eq!(query.items.len(), 1);
799
800        println!("\nAll builder API tests passed!");
801    }
802}