saluki_core/data_model/event/service_check/
mod.rs

1//! Service checks.
2
3use saluki_common::iter::ReusableDeduplicator;
4use serde::{ser::SerializeMap as _, Serialize, Serializer};
5use stringtheory::MetaString;
6
7use crate::data_model::tags::TagSet;
8
9/// Service status.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum CheckStatus {
12    /// The service is operating normally.
13    Ok,
14
15    /// The service is in a warning state.
16    Warning,
17
18    /// The service is in a critical state.
19    Critical,
20
21    /// The service is in an unknown state.
22    Unknown,
23}
24
25/// A service check.
26///
27/// Service checks represent the status of a service at a particular point in time. Checks are simplistic, with a basic
28/// message, status enum (OK vs warning vs critical, etc), timestamp, and tags.
29#[derive(Clone, Debug, PartialEq)]
30pub struct ServiceCheck {
31    name: MetaString,
32    status: CheckStatus,
33    timestamp: Option<u64>,
34    hostname: MetaString,
35    message: MetaString,
36    tags: TagSet,
37    origin_tags: TagSet,
38}
39
40impl ServiceCheck {
41    /// Returns the name of the check.
42    pub fn name(&self) -> &str {
43        &self.name
44    }
45
46    /// Returns the status of the check.
47    pub fn status(&self) -> CheckStatus {
48        self.status
49    }
50
51    /// Returns the timestamp of the check.
52    ///
53    /// This is a Unix timestamp, or the number of seconds since the Unix epoch.
54    pub fn timestamp(&self) -> Option<u64> {
55        self.timestamp
56    }
57
58    /// Returns the host where the check originated from.
59    pub fn hostname(&self) -> Option<&str> {
60        if self.hostname.is_empty() {
61            None
62        } else {
63            Some(&self.hostname)
64        }
65    }
66
67    /// Returns the message associated with the check.
68    pub fn message(&self) -> Option<&str> {
69        if self.message.is_empty() {
70            None
71        } else {
72            Some(&self.message)
73        }
74    }
75
76    /// Returns the tags associated with the check.
77    pub fn tags(&self) -> &TagSet {
78        &self.tags
79    }
80
81    /// Returns the origin tags associated with the check.
82    pub fn origin_tags(&self) -> &TagSet {
83        &self.origin_tags
84    }
85
86    /// Creates a `ServiceCheck` from the given name and status
87    pub fn new(name: impl Into<MetaString>, status: CheckStatus) -> Self {
88        Self {
89            name: name.into(),
90            status,
91            timestamp: None,
92            hostname: MetaString::empty(),
93            message: MetaString::empty(),
94            tags: TagSet::default(),
95            origin_tags: TagSet::default(),
96        }
97    }
98
99    /// Set the timestamp.
100    ///
101    /// Represented as a Unix timestamp, or the number of seconds since the Unix epoch.
102    ///
103    /// This variant is specifically for use in builder-style APIs.
104    pub fn with_timestamp(mut self, timestamp: impl Into<Option<u64>>) -> Self {
105        self.timestamp = timestamp.into();
106        self
107    }
108
109    /// Set the hostname where the service check originated from.
110    ///
111    /// This variant is specifically for use in builder-style APIs.
112    pub fn with_hostname(mut self, hostname: impl Into<Option<MetaString>>) -> Self {
113        self.hostname = match hostname.into() {
114            Some(hostname) => hostname,
115            None => MetaString::empty(),
116        };
117        self
118    }
119
120    /// Set the hostname where the service check originated from.
121    pub fn set_hostname(&mut self, hostname: impl Into<Option<MetaString>>) {
122        self.hostname = match hostname.into() {
123            Some(hostname) => hostname,
124            None => MetaString::empty(),
125        };
126    }
127
128    /// Set the tags of the service check
129    ///
130    /// This variant is specifically for use in builder-style APIs.
131    pub fn with_tags(mut self, tags: impl Into<TagSet>) -> Self {
132        self.tags = tags.into();
133        self
134    }
135
136    /// Set the message of the service check
137    ///
138    /// This variant is specifically for use in builder-style APIs.
139    pub fn with_message(mut self, message: impl Into<Option<MetaString>>) -> Self {
140        self.message = match message.into() {
141            Some(message) => message,
142            None => MetaString::empty(),
143        };
144        self
145    }
146
147    /// Set the origin tags of the service check
148    ///
149    /// This variant is specifically for use in builder-style APIs.
150    pub fn with_origin_tags(mut self, origin_tags: impl Into<TagSet>) -> Self {
151        self.origin_tags = origin_tags.into();
152        self
153    }
154}
155
156impl Serialize for ServiceCheck {
157    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
158    where
159        S: Serializer,
160    {
161        let mut map = serializer.serialize_map(None)?;
162        map.serialize_entry("check", &self.name)?;
163        if !self.hostname.is_empty() {
164            map.serialize_entry("host_name", &self.hostname)?;
165        }
166        if !self.message.is_empty() {
167            map.serialize_entry("message", &self.message)?;
168        }
169        map.serialize_entry("status", &self.status)?;
170
171        let tags = DeduplicatedTagsSerializable {
172            tags: &self.tags,
173            origin_tags: &self.origin_tags,
174        };
175        map.serialize_entry("tags", &tags)?;
176
177        if let Some(timestamp) = self.timestamp.as_ref() {
178            map.serialize_entry("timestamp", timestamp)?;
179        }
180        map.end()
181    }
182}
183
184impl CheckStatus {
185    /// Returns the integer representation of this status.
186    pub const fn as_u8(&self) -> u8 {
187        match self {
188            Self::Ok => 0,
189            Self::Warning => 1,
190            Self::Critical => 2,
191            Self::Unknown => 3,
192        }
193    }
194}
195
196impl Serialize for CheckStatus {
197    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198    where
199        S: Serializer,
200    {
201        serializer.serialize_u8(self.as_u8())
202    }
203}
204
205/// Error type for parsing CheckStatus.
206#[derive(Debug, Clone)]
207pub struct ParseCheckStatusError;
208
209impl std::fmt::Display for ParseCheckStatusError {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        write!(f, "invalid check status")
212    }
213}
214
215impl std::error::Error for ParseCheckStatusError {}
216
217impl TryFrom<u8> for CheckStatus {
218    type Error = ParseCheckStatusError;
219
220    fn try_from(value: u8) -> Result<Self, Self::Error> {
221        match value {
222            0 => Ok(Self::Ok),
223            1 => Ok(Self::Warning),
224            2 => Ok(Self::Critical),
225            3 => Ok(Self::Unknown),
226            _ => Err(ParseCheckStatusError),
227        }
228    }
229}
230
231// Helper type to let us serialize deduplicated tags.
232struct DeduplicatedTagsSerializable<'a> {
233    tags: &'a TagSet,
234    origin_tags: &'a TagSet,
235}
236
237impl<'a> Serialize for DeduplicatedTagsSerializable<'a> {
238    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
239    where
240        S: Serializer,
241    {
242        let chained_tags = self.tags.into_iter().chain(self.origin_tags);
243
244        let mut tags_deduplicator = ReusableDeduplicator::new();
245        let deduplicated_tags = tags_deduplicator.deduplicated(chained_tags);
246        serializer.collect_seq(deduplicated_tags)
247    }
248}