sentry_types/protocol/monitor.rs
1use std::{error::Error, fmt::Display};
2
3use serde::{Deserialize, Serialize, Serializer};
4use uuid::Uuid;
5
6use crate::crontab_validator;
7
8/// Error type for errors with parsing a crontab schedule
9#[derive(Debug)]
10pub struct CrontabParseError {
11 invalid_crontab: String,
12}
13
14impl Display for CrontabParseError {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(
17 f,
18 "\"{}\" is not a valid crontab schedule.\n\t \
19 For help determining why this schedule is invalid, you can use this site: \
20 https://crontab.guru/#{}",
21 self.invalid_crontab,
22 self.invalid_crontab
23 .split_whitespace()
24 .collect::<Vec<_>>()
25 .join("_"),
26 )
27 }
28}
29
30impl Error for CrontabParseError {}
31
32impl CrontabParseError {
33 /// Constructs a new CrontabParseError from a given invalid crontab string
34 ///
35 /// ## Example
36 /// ```
37 /// use sentry_types::protocol::v7::CrontabParseError;
38 ///
39 /// let error = CrontabParseError::new("* * * *");
40 /// ```
41 pub fn new(invalid_crontab: &str) -> Self {
42 Self {
43 invalid_crontab: String::from(invalid_crontab),
44 }
45 }
46}
47
48/// Represents the status of the monitor check-in
49#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
50#[serde(rename_all = "snake_case")]
51pub enum MonitorCheckInStatus {
52 /// Check-in had no issues during execution.
53 Ok,
54 /// Check-in failed or otherwise had some issues.
55 Error,
56 /// Check-in is expectred to complete.
57 InProgress,
58 /// Monitor did not check in on time.
59 Missed,
60 /// No status was passed.
61 #[serde(other)]
62 Unknown,
63}
64
65/// Configuration object of the monitor schedule.
66#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
67#[serde(rename_all = "snake_case")]
68#[serde(tag = "type")]
69pub enum MonitorSchedule {
70 /// A Crontab schedule allows you to use a standard UNIX crontab style schedule string to
71 /// configure when a monitor check-in will be expected on Sentry.
72 Crontab {
73 /// The crontab syntax string defining the schedule.
74 value: String,
75 },
76 /// A Interval schedule allows you to configure a periodic check-in, that will occur at an
77 /// interval after the most recent check-in.
78 Interval {
79 /// The interval value.
80 value: u64,
81 /// The interval unit of the value.
82 unit: MonitorIntervalUnit,
83 },
84}
85
86impl MonitorSchedule {
87 /// Attempts to create a MonitorSchedule from a provided crontab_str. If the crontab_str is a
88 /// valid crontab schedule, we return a Result containing the MonitorSchedule; otherwise, we
89 /// return a Result containing a CrontabParseError.
90 ///
91 /// ## Example with valid crontab
92 /// ```
93 /// use sentry_types::protocol::v7::MonitorSchedule;
94 ///
95 /// // Create a crontab that runs every other day of the month at midnight.
96 /// let result = MonitorSchedule::from_crontab("0 0 */2 * *");
97 /// assert!(result.is_ok())
98 /// ```
99 ///
100 /// ## Example with an invalid crontab
101 /// ```
102 /// use sentry_types::protocol::v7::MonitorSchedule;
103 ///
104 /// // Invalid crontab.
105 /// let result = MonitorSchedule::from_crontab("invalid");
106 /// assert!(result.is_err());
107 /// ```
108 pub fn from_crontab(crontab_str: &str) -> Result<Self, CrontabParseError> {
109 if crontab_validator::validate(crontab_str) {
110 Ok(Self::Crontab {
111 value: String::from(crontab_str),
112 })
113 } else {
114 Err(CrontabParseError::new(crontab_str))
115 }
116 }
117}
118
119/// The unit for the interval schedule type
120#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
121#[serde(rename_all = "snake_case")]
122pub enum MonitorIntervalUnit {
123 /// Year Interval.
124 Year,
125 /// Month Interval.
126 Month,
127 /// Week Interval.
128 Week,
129 /// Day Interval.
130 Day,
131 /// Hour Interval.
132 Hour,
133 /// Minute Interval.
134 Minute,
135}
136
137/// The monitor configuration playload for upserting monitors during check-in
138#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
139pub struct MonitorConfig {
140 /// The monitor schedule configuration.
141 pub schedule: MonitorSchedule,
142
143 /// How long (in minutes) after the expected check-in time will we wait until we consider the
144 /// check-in to have been missed.
145 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub checkin_margin: Option<u64>,
147
148 /// How long (in minutes) is the check-in allowed to run for in
149 /// [`MonitorCheckInStatus::InProgress`] before it is considered failed.in_rogress
150 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub max_runtime: Option<u64>,
152
153 /// tz database style timezone string
154 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub timezone: Option<String>,
156
157 /// The number of consecutive failed/error check-ins that triggers issue creation.
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub failure_issue_threshold: Option<u64>,
160
161 /// The number of consecutive successful check-ins that triggers issue resolution.
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub recovery_threshold: Option<u64>,
164}
165
166fn serialize_id<S: Serializer>(uuid: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
167 serializer.serialize_some(&uuid.as_simple())
168}
169
170/// The monitor check-in payload.
171#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
172pub struct MonitorCheckIn {
173 /// Unique identifier of this check-in.
174 #[serde(serialize_with = "serialize_id")]
175 pub check_in_id: Uuid,
176
177 /// Identifier of the monitor for this check-in.
178 pub monitor_slug: String,
179
180 /// Status of this check-in. Defaults to `"unknown"`.
181 pub status: MonitorCheckInStatus,
182
183 /// The environment to associate the check-in with.
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub environment: Option<String>,
186
187 /// Duration of this check-in since it has started in seconds.
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub duration: Option<f64>,
190
191 /// Monitor configuration to support upserts. When provided a monitor will be created on Sentry
192 /// upon receiving the first check-in.
193 ///
194 /// If the monitor already exists the configuration will be updated with the values provided in
195 /// this object.
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub monitor_config: Option<MonitorConfig>,
198}