Skip to main content

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}