Skip to main content

liminal_cli/network/
rail.rs

1//! The rail's contract: what the network is asked, its counts, and how it
2//! refuses.
3
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use serde_json::Value;
7
8/// How the network answered, or why it did not. The words of a refusal are
9/// the server's; the ticket adds none.
10#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
11pub enum RailError {
12    /// The server refused or could not answer; its words.
13    #[error("{0}")]
14    Refused(String),
15    /// A topology record larger than the configured limit.
16    #[error("a topology record over {limit} bytes was refused")]
17    RecordTooLarge {
18        /// The configured limit.
19        limit: usize,
20    },
21    /// No topology record arrived within the reply timeout.
22    #[error("no topology record arrived within {millis} ms")]
23    NoRecord {
24        /// The timeout waited.
25        millis: u64,
26    },
27    /// The record or the reply is not the JSON expected.
28    #[error("the network's answer could not be read: {message}")]
29    Unreadable {
30        /// The reader's words.
31        message: String,
32    },
33}
34
35/// What the ticket did on the network, counted (SDK-017 R5).
36#[derive(Debug, Default)]
37pub struct Counts {
38    /// Connections opened.
39    pub connections_opened: AtomicU64,
40    /// Connections closed.
41    pub connections_closed: AtomicU64,
42    /// Subscriptions opened on `console.topology`.
43    pub subscribes: AtomicU64,
44    /// Topology records received.
45    pub records: AtomicU64,
46    /// Requests sent on a subject.
47    pub requests: AtomicU64,
48    /// Replies received for them.
49    pub replies: AtomicU64,
50}
51
52impl Counts {
53    /// The six counts in order: opened, closed, subscribes, records,
54    /// requests, replies.
55    #[must_use]
56    pub fn read(&self) -> [u64; 6] {
57        [
58            self.connections_opened.load(Ordering::SeqCst),
59            self.connections_closed.load(Ordering::SeqCst),
60            self.subscribes.load(Ordering::SeqCst),
61            self.records.load(Ordering::SeqCst),
62            self.requests.load(Ordering::SeqCst),
63            self.replies.load(Ordering::SeqCst),
64        ]
65    }
66
67    pub(crate) fn add(counter: &AtomicU64) {
68        counter.fetch_add(1, Ordering::SeqCst);
69    }
70}
71
72/// The network as the tools see it.
73pub trait Rail: std::fmt::Debug {
74    /// The current topology record, read fresh: one subscription, the first
75    /// whole record, closed before returning.
76    ///
77    /// # Errors
78    /// Returns the server's refusal, an oversize record or no record.
79    fn topology(&self) -> Result<Value, RailError>;
80
81    /// One request on `subject`; with `reply` the correlated reply's JSON,
82    /// without it `None` once sent.
83    ///
84    /// # Errors
85    /// Returns the server's refusal in its words.
86    fn request(
87        &self,
88        subject: &str,
89        input: &Value,
90        reply: bool,
91    ) -> Result<Option<Value>, RailError>;
92
93    /// The calling seat's own pass for `audience`, minted for a call that
94    /// names `pass_for`; never the pass the rail's connections present.
95    ///
96    /// # Errors
97    /// Returns why no pass could be had, in words that carry no secret.
98    fn pass_for(&self, audience: &str) -> Result<String, RailError>;
99
100    /// Opens a long-lived subscription on a tap channel using the seat pass.
101    ///
102    /// # Errors
103    /// Returns the server's refusal in its words.
104    fn tap(&self, channel: &str) -> Result<super::TapSubscription, RailError>;
105
106    /// The configured server address used to identify notification records.
107    fn server_address(&self) -> &str;
108
109    /// The counts so far.
110    fn counts(&self) -> &Counts;
111
112    /// The largest request body the rail sends.
113    fn request_bytes(&self) -> usize;
114
115    /// The most steps one flow may carry.
116    fn flow_steps(&self) -> usize;
117}