watchbird_api 0.2.2-rc.0

A self-hosted and API-driven uptime monitor
Documentation
use aide::OperationIo;
use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display};
use thiserror::Error;
pub use uuid::Uuid;

use crate::v1alpha1::Probe;

pub type WatchId = Uuid;

/// A watch is a number of regular checks to perform, as well as alerts to ring if something happens.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, OperationIo)]
#[serde(rename_all = "camelCase")]
pub struct Watch {
    pub id: WatchId,
    /// User-readable identifier
    pub name: String,
    /// Unique identifier of this alerts owner. Will fire for watches with the same owner.
    pub owner_ref: String,
    pub config: WatchConfig,
    pub status: WatchStatus,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateWatch {
    /// User-readable identifier
    pub name: String,
    /// Unique identifier of this alerts owner. Will fire for watches with the same owner.
    pub owner_ref: String,
    pub config: WatchConfig,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Error)]
#[serde(rename_all = "camelCase")]
pub enum CreateWatchError {
    #[error("probes list must not be empty")]
    NoProbes,
    #[error("alerts list must not be empty")]
    NoAlerts,
}
impl CreateWatch {
    pub fn try_into_watch(self, id: WatchId) -> Result<Watch, CreateWatchError> {
        if self.config.probes.is_empty() {
            return Err(CreateWatchError::NoProbes);
        }
        Ok(Watch {
            id,
            name: self.name,
            status: WatchStatus::default(),
            owner_ref: self.owner_ref,
            config: self.config,
        })
    }
}
impl From<Watch> for CreateWatch {
    fn from(value: Watch) -> Self {
        CreateWatch {
            name: value.name,
            owner_ref: value.owner_ref,
            config: value.config,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct WatchConfig {
    /// Probes that this watch will execute
    pub probes: Vec<Probe>,
}

impl Display for Watch {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} ({})", self.name, self.id)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct WatchStatus {
    pub state: WatchState,
    #[serde(default)]
    pub last_update: Option<DateTime<Utc>>,
    #[serde(default)]
    pub failing_probes: Vec<String>,
}
impl WatchStatus {
    pub fn new(state: WatchState, failing_probes: Vec<&Probe>) -> WatchStatus {
        WatchStatus {
            state,
            last_update: Some(Utc::now()),
            failing_probes: failing_probes
                .into_iter()
                .map(|probe| {
                    probe
                        .description
                        .clone()
                        .unwrap_or_else(|| probe.to_string())
                })
                .collect(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
pub enum WatchState {
    #[default]
    Pending,
    Ok,
    Failing,
}
impl Display for WatchState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::Pending => "pending",
                Self::Ok => "ok",
                Self::Failing => "failing",
            }
        )
    }
}