use std::fmt;
#[derive(Debug, Clone, Copy)]
pub struct ErrorCode(pub u16);
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E{:04}", self.0)
}
}
impl std::error::Error for ErrorCode {}
impl ErrorCode {
pub fn doc_url(&self) -> String {
format!(
"https://ferrflow.com/docs/reference/errors#{}",
self.to_string().to_lowercase()
)
}
}
#[allow(dead_code)]
pub trait ErrorCodeExt<T> {
fn error_code(self, code: ErrorCode) -> anyhow::Result<T>;
}
impl<T> ErrorCodeExt<T> for anyhow::Result<T> {
fn error_code(self, code: ErrorCode) -> anyhow::Result<T> {
self.map_err(|e| e.context(code))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_formats_with_leading_zeros() {
assert_eq!(ErrorCode(1).to_string(), "E0001");
assert_eq!(ErrorCode(42).to_string(), "E0042");
assert_eq!(ErrorCode(1001).to_string(), "E1001");
assert_eq!(ErrorCode(9999).to_string(), "E9999");
}
#[test]
fn doc_url_uses_lowercase() {
let url = ErrorCode(1001).doc_url();
assert_eq!(url, "https://ferrflow.com/docs/reference/errors#e1001");
}
#[test]
fn error_code_ext_attaches_code() {
let err: anyhow::Result<()> = Err(anyhow::anyhow!("something broke"));
let err = err.error_code(ErrorCode(2001));
let err = err.unwrap_err();
let code = err.downcast_ref::<ErrorCode>().copied();
assert!(code.is_some());
assert_eq!(code.unwrap().0, 2001);
}
#[test]
fn error_without_code_returns_none() {
let err = anyhow::anyhow!("plain error");
let code = err.downcast_ref::<ErrorCode>().copied();
assert!(code.is_none());
}
}