use serde::{Deserialize, Serialize};
pub type Result<T> = std::result::Result<T, Error>;
const API_URL: &str = "https://uptime.betterstack.com/api";
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("reqwest: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("Uptime API: {0}")]
UptimeApi(String),
#[error("serde_json: {0}")]
SerdeJson(#[from] serde_json::Error),
}
fn maybe_uptime_api_error<T>(value: serde_json::Value) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
#[derive(Deserialize)]
struct ErrorResponse {
error: String,
}
if let Ok(ErrorResponse { error }) = serde_json::from_value::<ErrorResponse>(value.clone()) {
Err(Error::UptimeApi(error))
} else {
serde_json::from_value(value).map_err(|err| err.into())
}
}
pub struct Uptime {
pub token: String,
}
#[derive(Debug, Serialize, Default)]
pub struct IncidentRequest {
pub requester_email: String,
pub name: String,
pub summary: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub call: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sms: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub push: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub team_wait: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub poliy_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Incident {
pub id: String,
}
impl Uptime {
pub async fn heartbeat(&self, identifier: String) -> Result<()> {
let url = format!("{API_URL}/v1/heartbeat/{identifier}");
match reqwest::get(url).await?.error_for_status() {
Ok(_) => Ok(()),
Err(err) => Err(err.into()),
}
}
pub async fn create_incident(&self, request: IncidentRequest) -> Result<Incident> {
let url = format!("{API_URL}/v2/incidents");
#[derive(Debug, Deserialize)]
struct IncidentResponse {
data: Incident,
}
let response = maybe_uptime_api_error::<IncidentResponse>(
reqwest::Client::builder()
.build()?
.post(url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", self.token))
.json(&request)
.send()
.await?
.error_for_status()?
.json()
.await?,
)?;
Ok(response.data)
}
}