1use 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#[derive(Clone)]
25pub struct StreamCancelHandle(Arc<dyn Fn() + Send + Sync>);
26
27impl StreamCancelHandle {
28 pub fn new<F>(f: F) -> Self
30 where
31 F: Fn() + Send + Sync + 'static,
32 {
33 StreamCancelHandle(Arc::new(f))
34 }
35
36 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
48pub trait DcbEventStoreSync {
50 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 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 fn head(&self) -> DcbResult<Option<u64>>;
81
82 fn get_tracking_info(&self, source: &str) -> DcbResult<Option<u64>>;
84
85 fn append(
89 &self,
90 events: Vec<DcbEvent>,
91 condition: Option<DcbAppendCondition>,
92 tracking_info: Option<TrackingInfo>,
93 ) -> DcbResult<u64>;
94}
95
96pub trait DcbReadResponseSync: Iterator<Item = DcbResult<DcbSequencedEvent>> + Send {
98 fn head(&mut self) -> DcbResult<Option<u64>>;
100 fn collect_with_head(&mut self) -> DcbResult<(Vec<DcbSequencedEvent>, Option<u64>)>;
102 fn next_batch(&mut self) -> DcbResult<Vec<DcbSequencedEvent>>;
107
108 fn cancel(&mut self) {}
115
116 fn cancel_handle(&self) -> Option<StreamCancelHandle> {
121 None
122 }
123
124 fn next_timeout(&mut self, _timeout: Duration) -> Option<DcbResult<DcbSequencedEvent>> {
125 self.next()
127 }
128}
129
130pub trait DcbSubscriptionSync: Iterator<Item = DcbResult<DcbSequencedEvent>> + Send {
132 fn next_batch(&mut self) -> DcbResult<Vec<DcbSequencedEvent>>;
136 fn next_batch_timeout(&mut self, timeout: Duration) -> DcbResult<Vec<DcbSequencedEvent>>;
137
138 fn cancel(&mut self);
145
146 fn next_timeout(&mut self, _timeout: Duration) -> Option<DcbResult<DcbSequencedEvent>> {
148 self.next()
150 }
151}
152
153#[async_trait]
155pub trait DcbEventStoreAsync: Send + Sync {
156 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 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 async fn head(&self) -> DcbResult<Option<u64>>;
187
188 async fn get_tracking_info(&self, source: &str) -> DcbResult<Option<u64>>;
190
191 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#[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?); }
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 fn cancel(&mut self);
234
235 fn cancel_handle(&self) -> Option<StreamCancelHandle> {
238 None
239 }
240
241 fn check_shutdown_status(&self) -> ShutdownStatus;
242}
243
244#[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 fn cancel(&mut self) {}
256
257 fn cancel_handle(&self) -> Option<StreamCancelHandle> {
260 None
261 }
262 fn check_shutdown_status(&self) -> ShutdownStatus;
263}
264
265#[derive(Debug, Clone, Default)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
268pub struct DcbQueryItem {
269 pub types: Vec<String>,
271 pub tags: Vec<String>,
273}
274
275impl DcbQueryItem {
276 pub fn new() -> Self {
278 Self {
279 types: vec![],
280 tags: vec![],
281 }
282 }
283
284 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 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#[derive(Debug, Clone, Default)]
307#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
308pub struct DcbQuery {
309 pub items: Vec<DcbQueryItem>,
311}
312
313impl DcbQuery {
314 pub fn new() -> Self {
316 Self { items: Vec::new() }
317 }
318
319 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 pub fn item(mut self, item: DcbQueryItem) -> Self {
331 self.items.push(item);
332 self
333 }
334
335 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#[derive(Debug, Clone, Default)]
347#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
348pub struct DcbAppendCondition {
349 pub fail_if_events_match: DcbQuery,
351 pub after: Option<u64>,
353}
354
355impl DcbAppendCondition {
356 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#[derive(Debug, Clone)]
372#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
373pub struct DcbEvent {
374 pub event_type: String,
376 pub tags: Vec<String>,
378 pub data: Vec<u8>,
380 pub uuid: Option<Uuid>,
382 pub metadata: Vec<(String, String)>,
384}
385
386impl Default for DcbEvent {
387 fn default() -> Self {
388 Self::new()
389 }
390}
391
392impl DcbEvent {
393 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 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 pub fn data<D: Into<Vec<u8>>>(mut self, data: D) -> Self {
412 self.data = data.into();
413 self
414 }
415
416 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 pub fn uuid(mut self, uuid: Uuid) -> Self {
428 self.uuid = Some(uuid);
429 self
430 }
431
432 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 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#[derive(Debug, Clone)]
474#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
475pub struct DcbSequencedEvent {
476 pub position: u64,
478 pub event: DcbEvent,
480}
481
482#[derive(Error, Debug)]
484#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
485pub enum DcbError {
486 #[error("io error: {0}")]
488 #[cfg_attr(feature = "serde", serde(with = "serde_io_error"))]
489 Io(#[from] std::io::Error),
490
491 #[error("integrity error: condition failed: {0}")]
493 IntegrityError(String),
494 #[error("corruption detected: {0}")]
495 Corruption(String),
496 #[error("invalid argument: {0}")]
498 InvalidArgument(String),
499
500 #[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 #[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 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 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 let mut response =
747 TestReadResponse::new(vec![seq_event1.clone(), seq_event2.clone()], Some(2));
748
749 assert_eq!(response.head().unwrap(), Some(2));
751
752 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 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 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 let event3 = DcbEvent::default().event_type("type3");
786 assert_eq!(event3.data.len(), 0);
787 assert_eq!(event3.tags.len(), 0);
788
789 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 let query = DcbQuery::new().item(query_item);
798 assert_eq!(query.items.len(), 1);
799
800 println!("\nAll builder API tests passed!");
801 }
802}