shep_core/protocol/request/dog_source.rs
1//! Where a dog came from, as a flock row carries it.
2
3use serde::{Deserialize, Serialize};
4
5// Named by an intra-doc link and by nothing rustc compiles.
6#[cfg(doc)]
7use super::ProcessInfo;
8
9/// Where a dog came from: this binary, or one an operator adopted.
10///
11/// Carried on [`ProcessInfo::dog`], so a listing distinguishes the two
12/// populations without a second request.
13// wire format: changing existing variants is a breaking change
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(tag = "kind", rename_all = "snake_case")]
16#[non_exhaustive]
17pub enum DogSource {
18 /// An argv branch of the shep binary itself (`shep dog <name>`).
19 BuiltIn,
20 /// A binary an operator adopted, run at the daemon's own trust level.
21 Adopted {
22 /// The binary's path, exactly as the operator gave it to `adopt`.
23 path: String,
24 /// Whether the binary asked for the shepherd channel when it was
25 /// adopted, with a `shep-channel: true` line in its `--version`
26 /// answer. Such a dog is spawned with `channel` and
27 /// `shutdown_with_message` set. Absent on the wire when `false`.
28 #[serde(default, skip_serializing_if = "core::ops::Not::not")]
29 channel: bool,
30 },
31}
32
33impl DogSource {
34 /// The kind's one-word label, `built-in` or `adopted`.
35 ///
36 /// Never the adopted path: this is what a SOURCE column shows, and the
37 /// path is on the variant for a caller that wants it.
38 const fn as_str(&self) -> &'static str {
39 match self {
40 DogSource::BuiltIn => "built-in",
41 DogSource::Adopted { .. } => "adopted",
42 }
43 }
44}
45
46/// The kind's label, for a caller rendering a source into a cell or a
47/// metric value. Takes a borrow, since [`DogSource::Adopted`] owns a
48/// `String` that the label does not read.
49impl From<&DogSource> for &'static str {
50 fn from(source: &DogSource) -> Self {
51 source.as_str()
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn a_dog_source_serializes_snake_case_under_its_kind() {
61 assert_eq!(
62 serde_json::to_string(&DogSource::BuiltIn).unwrap(),
63 r#"{"kind":"built_in"}"#
64 );
65 let adopted = DogSource::Adopted {
66 path: "/usr/local/bin/shep-otel".to_string(),
67 channel: false,
68 };
69 let wire = r#"{"kind":"adopted","path":"/usr/local/bin/shep-otel"}"#;
70 assert_eq!(serde_json::to_string(&adopted).unwrap(), wire);
71 assert_eq!(serde_json::from_str::<DogSource>(wire).unwrap(), adopted);
72 }
73
74 /// The row above is also the shape every peer before `channel` wrote,
75 /// so it pins that an older peer's row still reads as no channel.
76 #[test]
77 fn an_adopted_dog_that_asked_for_the_channel_says_so_on_the_wire() {
78 let adopted = DogSource::Adopted {
79 path: "/usr/local/bin/shep-otel".to_string(),
80 channel: true,
81 };
82 let wire = r#"{"kind":"adopted","path":"/usr/local/bin/shep-otel","channel":true}"#;
83 assert_eq!(serde_json::to_string(&adopted).unwrap(), wire);
84 assert_eq!(serde_json::from_str::<DogSource>(wire).unwrap(), adopted);
85 }
86}