use reqwest::Client;
use std::sync::Arc;
use super::{ErrorEvent, ErrorNotifier};
#[cfg(feature = "notify-error-slack")]
use super::providers::SlackProvider;
#[cfg(feature = "notify-error-discord")]
use super::providers::DiscordProvider;
#[cfg(feature = "notify-error-ntfy")]
use super::providers::NtfyProvider;
#[cfg(feature = "notify-error-cmdline")]
use super::providers::CmdlineProvider;
pub struct NotificationManager {
providers: Vec<Arc<dyn ErrorNotifier>>,
client: Client,
}
impl NotificationManager {
pub fn new() -> Self {
Self {
providers: Vec::new(),
client: Client::new(),
}
}
pub fn builder() -> NotificationManagerBuilder {
NotificationManagerBuilder::new()
}
pub fn from_env() -> Self {
let mut builder = Self::builder();
#[cfg(feature = "notify-error-slack")]
if let Ok(url) = std::env::var("SLACK_ERROR_WEBHOOK_URL") {
use super::providers::SlackConfig;
let mut config = SlackConfig::new(url);
if let Ok(mention) = std::env::var("SLACK_ERROR_MENTION") {
config = config.with_mention(mention);
}
builder = builder.with_slack(config);
}
#[cfg(feature = "notify-error-discord")]
if let Ok(url) = std::env::var("DISCORD_ERROR_WEBHOOK_URL") {
use super::providers::DiscordConfig;
let mut config = DiscordConfig::new(url);
if let Ok(mention) = std::env::var("DISCORD_ERROR_MENTION") {
config = config.with_mention(mention);
}
builder = builder.with_discord(config);
}
#[cfg(feature = "notify-error-ntfy")]
if let Ok(topic) = std::env::var("NTFY_TOPIC") {
use super::providers::NtfyConfig;
builder = builder.with_ntfy(NtfyConfig {
server_url: std::env::var("NTFY_SERVER_URL")
.unwrap_or_else(|_| "https://ntfy.sh".into()),
topic,
access_token: std::env::var("NTFY_ACCESS_TOKEN").ok(),
});
}
#[cfg(feature = "notify-error-cmdline")]
if let Ok(api_key) = std::env::var("CMDLINE_API_KEY") {
use super::providers::CmdlineConfig;
let mut config = CmdlineConfig::new(api_key);
if let Ok(env) = std::env::var("ENVIRONMENT").or_else(|_| std::env::var("ENV")) {
config = config.with_environment(env);
}
if let Ok(service) = std::env::var("SERVICE").or_else(|_| std::env::var("APP_NAME")) {
config = config.with_service(service);
}
if let Ok(release) = std::env::var("RELEASE").or_else(|_| std::env::var("VERSION")) {
config = config.with_release(release);
}
builder = builder.with_cmdline(config);
}
builder.build()
}
pub fn has_providers(&self) -> bool {
!self.providers.is_empty()
}
pub fn provider_count(&self) -> usize {
self.providers.len()
}
pub async fn notify(&self, event: &ErrorEvent) {
if self.providers.is_empty() {
return;
}
let futures: Vec<_> = self
.providers
.iter()
.map(|provider| {
let client = self.client.clone();
let provider = Arc::clone(provider);
let event = event.clone();
async move {
let name = provider.name();
if let Err(err) = provider.notify(&client, &event).await {
tracing::warn!(provider = name, error = %err, "notification failed");
}
}
})
.collect();
futures::future::join_all(futures).await;
}
}
impl Default for NotificationManager {
fn default() -> Self {
Self::new()
}
}
pub struct NotificationManagerBuilder {
providers: Vec<Arc<dyn ErrorNotifier>>,
}
impl NotificationManagerBuilder {
pub fn new() -> Self {
Self {
providers: Vec::new(),
}
}
pub fn with_provider(mut self, provider: Arc<dyn ErrorNotifier>) -> Self {
self.providers.push(provider);
self
}
#[cfg(feature = "notify-error-slack")]
pub fn with_slack(self, config: super::providers::SlackConfig) -> Self {
self.with_provider(Arc::new(SlackProvider::new(config)))
}
#[cfg(feature = "notify-error-discord")]
pub fn with_discord(self, config: super::providers::DiscordConfig) -> Self {
self.with_provider(Arc::new(DiscordProvider::new(config)))
}
#[cfg(feature = "notify-error-ntfy")]
pub fn with_ntfy(self, config: super::providers::NtfyConfig) -> Self {
self.with_provider(Arc::new(NtfyProvider::new(config)))
}
#[cfg(feature = "notify-error-cmdline")]
pub fn with_cmdline(self, config: super::providers::CmdlineConfig) -> Self {
self.with_provider(Arc::new(CmdlineProvider::new(config)))
}
pub fn build(self) -> NotificationManager {
NotificationManager {
providers: self.providers,
client: Client::new(),
}
}
}
impl Default for NotificationManagerBuilder {
fn default() -> Self {
Self::new()
}
}