use crate::error::AppError;
#[serde_with::skip_serializing_none]
#[derive(Debug, serde::Serialize)]
pub struct ErrorBody {
pub code: &'static str,
pub message: String,
pub category: &'static str,
pub retryable: bool,
pub hint: Option<&'static str>,
}
impl From<&AppError> for ErrorBody {
fn from(error: &AppError) -> Self {
Self {
code: error.code(),
message: error.to_string(),
category: error.category(),
retryable: error.retryable(),
hint: error.hint(),
}
}
}
#[cfg(test)]
mod tests {
use crate::error::AppError;
use crate::output::ErrorBody;
#[test]
fn error_body_from_app_error_maps_all_fields() {
let app_err = AppError::TokenFileMissing("/tmp/token.json".to_string());
let body = ErrorBody::from(&app_err);
assert_eq!(body.code, "auth.token_missing");
assert!(body.message.contains("token file not found"));
assert_eq!(body.category, "auth");
assert!(!body.retryable);
assert!(body.hint.is_some());
}
#[test]
fn error_body_without_hint_omits_hint_in_json() {
let app_err = AppError::Io(std::io::Error::other("oops"));
let body = ErrorBody::from(&app_err);
let serialized = serde_json::to_string(&body).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap();
assert!(parsed.get("hint").is_none());
}
}