use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ExitKind {
Generic = 1,
NotFound = 3,
HashMismatch = 4,
Validation = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorStream {
Stdout,
Stderr,
}
pub fn render_cli_error(
code: &str,
message: &str,
json_mode: bool,
details: Option<&serde_json::Value>,
) -> (ErrorStream, String) {
if json_mode {
let envelope = match details {
Some(d) => serde_json::json!({
"code": code,
"message": message,
"details": d,
}),
None => serde_json::json!({
"code": code,
"message": message,
}),
};
(
ErrorStream::Stdout,
serde_json::to_string(&envelope).unwrap_or_default(),
)
} else {
(
ErrorStream::Stderr,
format!("memstead: ERROR [{code}]: {message}"),
)
}
}
pub fn print_cli_error(
code: &str,
message: &str,
_kind: ExitKind,
json_mode: bool,
details: Option<&serde_json::Value>,
) {
let (stream, line) = render_cli_error(code, message, json_mode, details);
match stream {
ErrorStream::Stdout => println!("{line}"),
ErrorStream::Stderr => eprintln!("{line}"),
}
}
pub fn print_markdown(markdown: &str) {
println!("{markdown}");
}
pub fn print_json<T: Serialize>(value: &T) -> anyhow::Result<()> {
let s = serde_json::to_string_pretty(value)?;
println!("{s}");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn json_error_routes_to_stdout_with_code() {
let details = serde_json::json!({ "id": "test--missing" });
let (stream, line) =
render_cli_error("ENTITY_NOT_FOUND", "not found", true, Some(&details));
assert_eq!(stream, ErrorStream::Stdout);
let parsed: serde_json::Value = serde_json::from_str(&line).expect("valid JSON line");
assert_eq!(parsed["code"], "ENTITY_NOT_FOUND");
assert_eq!(parsed["details"]["id"], "test--missing");
}
#[test]
fn markdown_error_routes_to_stderr() {
let (stream, line) = render_cli_error("ENTITY_NOT_FOUND", "not found", false, None);
assert_eq!(stream, ErrorStream::Stderr);
assert!(line.starts_with("memstead: ERROR [ENTITY_NOT_FOUND]: "));
}
}