use std::time::Duration;
use runnerd_core::api::Notification;
use runnerd_core::config::{NotifyKind, NotifyTarget};
use super::{discord, ntfy, webhook};
pub(super) struct Post {
pub url: String,
pub body: Vec<u8>,
}
pub(super) trait Sender: Send + Sync {
fn post(&self, notification: &Notification) -> Result<Post, String>;
}
pub(super) fn sender(target: &NotifyTarget) -> Box<dyn Sender> {
match target.kind {
NotifyKind::Webhook => Box::new(webhook::Webhook::new(target.url.clone())),
NotifyKind::Ntfy => Box::new(ntfy::Ntfy::new(target.url.clone())),
NotifyKind::Discord => Box::new(discord::Discord::new(target.url.clone())),
}
}
pub(super) fn client(timeout: Duration) -> Result<reqwest::Client, String> {
runnerd_core::github::install_crypto_provider();
reqwest::Client::builder()
.user_agent(concat!("runnerd/", env!("CARGO_PKG_VERSION")))
.timeout(timeout)
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|build_error| build_error.to_string())
}
pub(super) async fn deliver(
http: &reqwest::Client,
target: &NotifyTarget,
notification: &Notification,
) -> Result<(), String> {
let Post { url, body } = sender(target).post(notification)?;
let mut request = http
.post(url)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(body);
for (name, value) in &target.headers {
request = request.header(name.as_str(), value.expose());
}
let response = request.send().await.map_err(|send| describe(&send))?;
let status = response.status();
if status.is_success() {
Ok(())
} else {
Err(format!("the target answered HTTP {status}"))
}
}
fn describe(send: &reqwest::Error) -> String {
if send.is_timeout() {
"the request timed out".to_owned()
} else if send.is_connect() {
"cannot connect to the target".to_owned()
} else if send.is_builder() {
"the url or a header cannot be sent".to_owned()
} else {
"the request failed".to_owned()
}
}