Skip to main content

max / everycycle

3.5 KB · 96 lines History Blame Raw
1 //! Transport shape.
2 //!
3 //! The IPC channel is a Unix domain socket. Each connection
4 //! exchanges length-prefixed CBOR frames: a 4-byte big-endian
5 //! length followed by that many bytes of CBOR. Clients send
6 //! `ClientRequest` frames; the daemon emits `ServerEvent` frames
7 //! tagged by `RequestId`. Framing details intentionally not exposed
8 //! at this layer — a future Cap'n Proto or QUIC transport should be
9 //! drop-in.
10
11 use std::path::{Path, PathBuf};
12
13 use crate::event::ServerEvent;
14 use crate::request::ClientRequest;
15
16 /// Default Unix socket the daemon binds and clients dial. Override
17 /// via `EVERYCYCLE_SOCKET` in either process.
18 #[must_use]
19 pub fn default_socket_path() -> PathBuf {
20 std::env::var_os("EVERYCYCLE_SOCKET").map_or_else(
21 || Path::new("/run/everycycle/api.sock").to_path_buf(),
22 PathBuf::from,
23 )
24 }
25
26 /// Maximum frame size in bytes. Larger payloads must be split by
27 /// the producer; the daemon refuses oversized frames. Constant
28 /// across pre-v1; raise carefully — bumping it is semver-minor but
29 /// shrinking it is semver-major.
30 pub const MAX_FRAME_BYTES: u32 = 16 * 1024 * 1024;
31
32 /// Length-prefix size in bytes. Big-endian u32.
33 pub const FRAME_LENGTH_BYTES: usize = 4;
34
35 /// Anything that can submit a request and receive a stream of events.
36 ///
37 /// Synchronous in shape because the daemon is local and the C-ABI
38 /// boundary cannot express Rust futures. An async wrapper around
39 /// this trait is a separate crate concern.
40 pub trait ClientTransport: Send + Sync {
41 /// Submit a request. Returns a stream of events for that
42 /// request. The stream terminates after the first event whose
43 /// `finished` discriminator (or equivalent terminal shape) is
44 /// set, or after an `Error` event.
45 ///
46 /// # Errors
47 ///
48 /// Transport-layer failures — connection lost, frame too
49 /// large, encoding error. Application-level failures arrive as
50 /// `ServerEvent::Error` events on the stream.
51 fn submit(&self, request: ClientRequest) -> Result<EventStream, TransportError>;
52 }
53
54 /// Opaque event-stream handle. Implementations choose the
55 /// concrete iterator type; this newtype keeps the trait
56 /// object-safe and the C-ABI translation tractable.
57 pub struct EventStream(pub Box<dyn Iterator<Item = Result<ServerEvent, TransportError>> + Send>);
58
59 impl Iterator for EventStream {
60 type Item = Result<ServerEvent, TransportError>;
61
62 fn next(&mut self) -> Option<Self::Item> {
63 self.0.next()
64 }
65 }
66
67 /// Errors raised by the transport layer itself (not by the daemon
68 /// rejecting a request — those arrive as `ServerEvent::Error`).
69 #[derive(Debug)]
70 pub enum TransportError {
71 /// Could not connect to the daemon socket.
72 Connect(std::io::Error),
73 /// Read or write failed mid-conversation.
74 Io(std::io::Error),
75 /// A frame exceeded `MAX_FRAME_BYTES`.
76 FrameTooLarge { size: u32 },
77 /// Encoding or decoding failure. String is the codec's report;
78 /// we do not bind to a specific codec at this layer.
79 Codec(String),
80 }
81
82 impl core::fmt::Display for TransportError {
83 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84 match self {
85 Self::Connect(e) => write!(f, "connect failed: {e}"),
86 Self::Io(e) => write!(f, "transport io: {e}"),
87 Self::FrameTooLarge { size } => {
88 write!(f, "frame size {size} exceeds {MAX_FRAME_BYTES}")
89 }
90 Self::Codec(s) => write!(f, "codec error: {s}"),
91 }
92 }
93 }
94
95 impl std::error::Error for TransportError {}
96