use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, NotifyError>;
#[derive(Debug, Error)]
pub enum NotifyError {
#[error("configuration file not found")]
ConfigNotFound,
#[error("failed to read configuration file {path}: {source}")]
ConfigRead {
path: PathBuf,
source: std::io::Error,
},
#[error("failed to parse configuration file {path}: {source}")]
ConfigParse {
path: PathBuf,
source: toml::de::Error,
},
#[error("channel \"{0}\" was not found")]
ChannelNotFound(String),
#[error("default_channel is not configured")]
DefaultChannelMissing,
#[error("{0}")]
InvalidInput(String),
#[error("{0}")]
Validation(String),
#[error("channel \"{channel}\" is missing environment variable {env}")]
MissingEnv { channel: String, env: String },
#[error("channel type \"{0}\" is not available for sending yet")]
UnsupportedProvider(String),
#[error("channel type \"{channel_type}\" does not support attachments")]
UnsupportedAttachment { channel_type: String },
#[error("HTTP request failed: {0}")]
Http(#[source] reqwest::Error),
#[error("provider request failed: {0}")]
Provider(String),
#[error("I/O error at {path}: {source}")]
Io {
path: PathBuf,
source: std::io::Error,
},
#[error("failed to serialize JSON output: {0}")]
Json(#[from] serde_json::Error),
}
impl From<reqwest::Error> for NotifyError {
fn from(source: reqwest::Error) -> Self {
Self::Http(source.without_url())
}
}
impl NotifyError {
pub fn code(&self) -> &'static str {
match self {
Self::ConfigNotFound => "CONFIG_NOT_FOUND",
Self::ConfigRead { .. } => "CONFIG_READ",
Self::ConfigParse { .. } => "CONFIG_PARSE",
Self::ChannelNotFound(_) => "CHANNEL_NOT_FOUND",
Self::DefaultChannelMissing => "DEFAULT_CHANNEL_MISSING",
Self::InvalidInput(_) => "INVALID_INPUT",
Self::Validation(_) => "VALIDATION",
Self::MissingEnv { .. } => "MISSING_ENV",
Self::UnsupportedProvider(_) => "UNSUPPORTED_PROVIDER",
Self::UnsupportedAttachment { .. } => "UNSUPPORTED_ATTACHMENT",
Self::Http(_) => "HTTP",
Self::Provider(_) => "PROVIDER",
Self::Io { .. } => "IO",
Self::Json(_) => "JSON",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn reqwest_errors_do_not_leak_request_url() {
let secret_url = "http://127.0.0.1:1/discord-webhook/secret-token";
let reqwest_error = reqwest::Client::new()
.post(secret_url)
.send()
.await
.unwrap_err();
assert!(reqwest_error.to_string().contains(secret_url));
let notify_error = NotifyError::from(reqwest_error);
let message = notify_error.to_string();
assert!(message.contains("HTTP request failed"));
assert!(!message.contains(secret_url));
assert!(!message.contains("secret-token"));
}
}