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}