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}