pub type Result<T> = std::result::Result<T, GhostError>;
#[derive(thiserror::Error, Debug)]
pub enum GhostError {
#[error("Ghost API error ({error_type}): {message}")]
Api {
message: String,
error_type: String,
context: Option<String>,
},
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("Serialization error: {0}")]
Json(#[from] serde_json::Error),
#[error("Authentication error: {0}")]
Auth(String),
}
impl GhostError {
pub fn api(
message: impl Into<String>,
error_type: impl Into<String>,
context: Option<String>,
) -> Self {
Self::Api {
message: message.into(),
error_type: error_type.into(),
context,
}
}
pub fn auth(message: impl Into<String>) -> Self {
Self::Auth(message.into())
}
pub fn is_api_error(&self) -> bool {
matches!(self, Self::Api { .. })
}
pub fn is_http_error(&self) -> bool {
matches!(self, Self::Http(_))
}
pub fn is_json_error(&self) -> bool {
matches!(self, Self::Json(_))
}
pub fn is_auth_error(&self) -> bool {
matches!(self, Self::Auth(_))
}
pub fn api_error_type(&self) -> Option<&str> {
match self {
Self::Api { error_type, .. } => Some(error_type),
_ => None,
}
}
pub fn api_message(&self) -> Option<&str> {
match self {
Self::Api { message, .. } => Some(message),
_ => None,
}
}
pub fn api_context(&self) -> Option<&str> {
match self {
Self::Api { context, .. } => context.as_deref(),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_error_creation() {
let error = GhostError::api("Resource not found", "NotFoundError", None);
assert!(error.is_api_error());
assert_eq!(error.api_error_type(), Some("NotFoundError"));
assert_eq!(error.api_message(), Some("Resource not found"));
assert_eq!(error.api_context(), None);
}
#[test]
fn test_api_error_with_context() {
let error = GhostError::api(
"Validation failed",
"ValidationError",
Some("Title is required".to_string()),
);
assert!(error.is_api_error());
assert_eq!(error.api_error_type(), Some("ValidationError"));
assert_eq!(error.api_message(), Some("Validation failed"));
assert_eq!(error.api_context(), Some("Title is required"));
}
#[test]
fn test_auth_error_creation() {
let error = GhostError::auth("Invalid API key");
assert!(error.is_auth_error());
assert!(!error.is_api_error());
assert_eq!(error.api_error_type(), None);
}
#[test]
fn test_http_error_from_trait() {
fn _assert_from_impl(e: reqwest::Error) -> GhostError {
e.into()
}
}
#[test]
fn test_json_error_conversion() {
let json_error = serde_json::from_str::<serde_json::Value>("not valid json").unwrap_err();
let error: GhostError = json_error.into();
assert!(error.is_json_error());
assert!(!error.is_api_error());
}
#[test]
fn test_error_display() {
let error = GhostError::api("Not found", "NotFoundError", None);
let display = format!("{}", error);
assert!(display.contains("NotFoundError"));
assert!(display.contains("Not found"));
}
#[test]
fn test_error_display_with_context() {
let error = GhostError::api(
"Validation failed",
"ValidationError",
Some("Title required".to_string()),
);
let display = format!("{}", error);
assert!(display.contains("ValidationError"));
assert!(display.contains("Validation failed"));
}
#[test]
fn test_auth_error_display() {
let error = GhostError::auth("JWT generation failed");
let display = format!("{}", error);
assert!(display.contains("Authentication error"));
assert!(display.contains("JWT generation failed"));
}
#[test]
fn test_result_type_alias() {
fn returns_result() -> Result<String> {
Ok("success".to_string())
}
let result = returns_result();
assert!(result.is_ok());
assert_eq!(result.unwrap(), "success");
}
#[test]
fn test_result_with_error() {
fn returns_error() -> Result<String> {
Err(GhostError::auth("Failed"))
}
let result = returns_error();
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.is_auth_error());
}
#[test]
fn test_error_type_checks() {
let api_error = GhostError::api("Test", "TestError", None);
assert!(api_error.is_api_error());
assert!(!api_error.is_http_error());
assert!(!api_error.is_json_error());
assert!(!api_error.is_auth_error());
let auth_error = GhostError::auth("Test");
assert!(!auth_error.is_api_error());
assert!(!auth_error.is_http_error());
assert!(!auth_error.is_json_error());
assert!(auth_error.is_auth_error());
}
#[test]
fn test_api_error_accessors() {
let error = GhostError::api("Test message", "TestType", Some("Test context".to_string()));
assert_eq!(error.api_message(), Some("Test message"));
assert_eq!(error.api_error_type(), Some("TestType"));
assert_eq!(error.api_context(), Some("Test context"));
}
#[test]
fn test_non_api_error_accessors_return_none() {
let error = GhostError::auth("Test");
assert_eq!(error.api_message(), None);
assert_eq!(error.api_error_type(), None);
assert_eq!(error.api_context(), None);
}
#[test]
fn test_error_is_send_and_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<GhostError>();
assert_sync::<GhostError>();
}
#[test]
fn test_error_debug() {
let error = GhostError::api("Debug test", "DebugError", None);
let debug = format!("{:?}", error);
assert!(debug.contains("Api"));
assert!(debug.contains("Debug test"));
assert!(debug.contains("DebugError"));
}
}