shep_core/protocol/request/outcomes.rs
1//! Per-row outcome and reply pairs for the action, signal, and line verbs.
2
3use serde::{Deserialize, Serialize};
4
5// Named by intra-doc links and by nothing rustc compiles, so the
6// import is behind `cfg(doc)` rather than flagged unused.
7#[cfg(doc)]
8use super::ProcessInfo;
9
10/// What happened when the daemon tried to deliver one sheep's triggered
11/// action.
12// wire format: changing existing variants is a breaking change
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(tag = "kind", rename_all = "snake_case")]
15#[non_exhaustive]
16pub enum ActionOutcome {
17 /// The app answered on the shepherd channel.
18 Replied {
19 /// The reply body, exactly as the app sent it.
20 body: String,
21 },
22 /// The sheep had no reachable shepherd channel for the daemon to
23 /// deliver the action over.
24 NoChannel,
25 /// The sheep is a reload drainee, mid-swap and on its way out, so the
26 /// daemon skipped it rather than deliver the action to a process already
27 /// being replaced.
28 Skipped,
29 /// The daemon delivered the action, but no reply arrived before the
30 /// app's configured action timeout elapsed.
31 TimedOut,
32 /// The sheep is a dog spawned without a shepherd channel, because its
33 /// `--version` answer did not ask for one when it was adopted. No config
34 /// key gives a dog the channel: only the dog can ask.
35 DogNoChannel,
36}
37
38/// One matched sheep's row in a `Trigger` reply.
39///
40/// Not a [`ProcessInfo`]: a reply body has nowhere to live on one.
41/// [`Self::outcome`] is per-row, since the selector grammar makes a mixed
42/// flock the normal case.
43// wire format: changing this is a breaking change
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct ActionReply {
46 /// The sheep's stable id.
47 pub id: u32,
48 /// The sheep's name.
49 pub name: String,
50 /// What happened when the daemon tried to deliver the action.
51 pub outcome: ActionOutcome,
52}
53
54/// What happened when the shepherd tried to deliver one signal.
55// wire format: changing existing variants is a breaking change
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(tag = "kind", rename_all = "snake_case")]
58#[non_exhaustive]
59pub enum SignalOutcome {
60 /// The kernel accepted the signal for this sheep's pid.
61 ///
62 /// Says the signal was delivered, not that the app did anything with it.
63 /// A signal the app blocks or ignores is `Delivered` too.
64 Delivered,
65 /// The sheep is registered but has no live process to signal: stopped,
66 /// errored, or waiting out a restart backoff.
67 NotRunning,
68 /// The kernel refused the delivery; carries its reason (`ESRCH` for a
69 /// process reaped between the lookup and the syscall, `EPERM` for one this
70 /// daemon may not signal).
71 Failed {
72 /// The refusal, as the OS worded it.
73 reason: String,
74 },
75}
76
77/// One matched sheep's row in a `Signal` reply.
78///
79/// Per-row like [`ActionReply`]: the selector grammar makes a mixed flock
80/// the normal case.
81// wire format: changing this is a breaking change
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct SignalReply {
84 /// The sheep's stable id.
85 pub id: u32,
86 /// The sheep's name.
87 pub name: String,
88 /// What happened when the shepherd tried to deliver the signal.
89 pub outcome: SignalOutcome,
90}
91
92/// What happened when the shepherd tried to write one line to a sheep's stdin.
93// wire format: changing existing variants is a breaking change
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
95#[serde(tag = "kind", rename_all = "snake_case")]
96#[non_exhaustive]
97pub enum LineOutcome {
98 /// The line was written to the pipe and flushed.
99 ///
100 /// Says the bytes left the shepherd, not that the app read them. A pipe
101 /// holds 64 KiB before it blocks.
102 Sent,
103 /// The sheep has no stdin pipe: its config does not set `stdin = true`, or
104 /// it is not running.
105 ///
106 /// One outcome for two causes: both answer "there is no pipe here".
107 NoStdin,
108 /// The shepherd had a pipe and did not confirm a write to it; carries
109 /// why.
110 ///
111 /// Three shapes reach it: the write failed (the far end is gone), the
112 /// line found the sheep's queue already full, or the write did not
113 /// finish inside the shepherd's own bound. The reason names which.
114 ///
115 /// A timed-out write is not a promise the line was never written: the
116 /// bytes may be part-written into a pipe the app is not draining, and
117 /// land in full the moment it drains. A line still queued behind that one
118 /// is dropped once its caller gives up, so treat a retry as a second
119 /// command.
120 NotWritten {
121 /// What went wrong, in plain English.
122 reason: String,
123 },
124}
125
126/// One matched sheep's row in a `SendLine` reply.
127///
128/// Per-row like [`ActionReply`] and [`SignalReply`].
129// wire format: changing this is a breaking change
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct LineReply {
132 /// The sheep's stable id.
133 pub id: u32,
134 /// The sheep's name.
135 pub name: String,
136 /// What happened.
137 pub outcome: LineOutcome,
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn action_outcome_kinds_serialize_snake_case_and_round_trip() {
146 // The shared snapshots exercise only `Replied`, the struct-shaped
147 // variant.
148 let cases = [
149 (
150 ActionOutcome::Replied {
151 body: "pong".to_string(),
152 },
153 r#"{"kind":"replied","body":"pong"}"#,
154 ),
155 (ActionOutcome::NoChannel, r#"{"kind":"no_channel"}"#),
156 (ActionOutcome::Skipped, r#"{"kind":"skipped"}"#),
157 (ActionOutcome::TimedOut, r#"{"kind":"timed_out"}"#),
158 (ActionOutcome::DogNoChannel, r#"{"kind":"dog_no_channel"}"#),
159 ];
160 for (outcome, wire) in cases {
161 assert_eq!(
162 serde_json::to_string(&outcome).unwrap(),
163 wire,
164 "{outcome:?}"
165 );
166 assert_eq!(
167 serde_json::from_str::<ActionOutcome>(wire).unwrap(),
168 outcome
169 );
170 }
171 }
172}