Skip to main content

structfs_handles/
lib.rs

1//! # structfs-handles
2//!
3//! Handle-store and streaming primitives for StructFS.
4//!
5//! The deferred-operation pattern — write a request, get an
6//! `outstanding/{id}` handle path back, read the handle for results — is
7//! the backbone of every broker-shaped StructFS store. This crate makes it
8//! a primitive instead of a convention:
9//!
10//! - [`HandleStore`] + [`HandleProtocol`]: generic `outstanding/{id}`
11//!   scaffolding — id minting, routing, the no-overwrite rule, Null-write
12//!   release with cancellation, listing.
13//! - [`TailLog`] / [`TailPage`]: append-only event streams with **atomic
14//!   tail reads** — items and terminal status in one operation, so the
15//!   "close-out drain" race cannot exist.
16//! - [`Gate`] / [`CancelToken`]: park-until-predicate with the
17//!   enable-before-check ordering baked in (no lost wakeups), and
18//!   cancellation that fails parked reads while leaving writes open.
19//! - `SyncBridge` (feature `sync-bridge`): run a detached async store from synchronous code on a
20//!   blocking thread.
21//! - [`conformance`]: certify any handle store against the protocol rules.
22
23mod duplex;
24pub use duplex::{DuplexStream, StreamReadiness, StreamStore};
25mod byte_stream;
26mod gate;
27mod handle_store;
28#[cfg(feature = "sync-bridge")]
29mod sync_bridge;
30mod tail;
31
32pub mod conformance;
33
34pub use byte_stream::{ByteChunk, ByteStream};
35pub use gate::{CancelToken, Cancelled, Gate};
36pub use handle_store::{HandleCx, HandleProtocol, HandleStore};
37#[cfg(feature = "sync-bridge")]
38pub use sync_bridge::SyncBridge;
39pub use tail::{TailLog, TailPage};
40
41// Re-export the async trait surface these types implement.
42pub use structfs_core_store::{
43    DetachedFuture, DetachedReader, DetachedStore, DetachedWriter, Error, Path, Record, Value,
44};
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use std::sync::Arc;
50
51    struct NullProtocol;
52
53    impl HandleProtocol for NullProtocol {
54        type Handle = Value;
55
56        fn open(&self, _cx: HandleCx, request: Value) -> Result<Self::Handle, Error> {
57            Ok(request)
58        }
59
60        fn read(&self, handle: Arc<Self::Handle>, _sub: Path) -> DetachedFuture<Option<Record>> {
61            Box::pin(async move { Ok(Some(Record::parsed((*handle).clone()))) })
62        }
63
64        fn write(
65            &self,
66            _handle: Arc<Self::Handle>,
67            sub: Path,
68            _data: Record,
69        ) -> DetachedFuture<Path> {
70            Box::pin(async move { Ok(sub) })
71        }
72    }
73
74    #[tokio::test]
75    async fn handle_store_passes_conformance() {
76        let mut store = HandleStore::new(NullProtocol);
77        conformance::check_handle_conventions(&mut store, Value::from("request")).await;
78    }
79}