use std::io::Write as _;
use std::process::ExitCode;
use crate::json::Json;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ErrorClass {
Actionable,
System,
}
impl ErrorClass {
const fn exit_code(self) -> u8 {
match self {
Self::Actionable => 1,
Self::System => 2,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CliError {
code: &'static str,
message: String,
class: ErrorClass,
}
impl CliError {
pub(crate) fn actionable(code: &'static str, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
class: ErrorClass::Actionable,
}
}
pub(crate) fn system(code: &'static str, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
class: ErrorClass::System,
}
}
}
pub(crate) fn json_requested(args: &[String]) -> bool {
args.iter()
.take_while(|arg| arg.as_str() != "--")
.any(|arg| arg == "--json")
}
pub(crate) fn fail(json: bool, error: CliError) -> ExitCode {
if json {
eprintln!(
"{}",
Json::Object(vec![
("schema_version".into(), Json::Int(1)),
(
"error".into(),
Json::Object(vec![
("code".into(), Json::str(error.code)),
("message".into(), Json::str(error.message)),
]),
),
])
);
} else {
eprintln!("project-canon: {}", error.message);
}
ExitCode::from(error.class.exit_code())
}
pub(crate) fn write_stdout(content: &str, json: bool) -> ExitCode {
let mut out = std::io::stdout().lock();
match out.write_all(content.as_bytes()) {
Ok(()) => ExitCode::SUCCESS,
Err(error) if error.kind() == std::io::ErrorKind::BrokenPipe => ExitCode::SUCCESS,
Err(error) => fail(
json,
CliError::system("io_error", format!("writing stdout: {error}")),
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recognizes_only_the_valueless_json_flag() {
assert!(json_requested(&["--json".into()]));
assert!(!json_requested(&["--json=false".into()]));
}
}