use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum ExitCode {
Success = 0,
Runtime = 1,
Usage = 2,
NotAStore = 3,
Policy = 4,
Collision = 5,
ValidationFailed = 6,
NotImplemented = 64,
}
impl ExitCode {
pub fn code(self) -> i32 {
self as i32
}
}
#[derive(Debug, Clone)]
pub struct CliError {
pub exit: ExitCode,
pub code: &'static str,
pub message: String,
pub hint: Option<String>,
}
impl CliError {
pub fn new(exit: ExitCode, code: &'static str, message: impl Into<String>) -> Self {
Self {
exit,
code,
message: message.into(),
hint: None,
}
}
pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
self.hint = Some(hint.into());
self
}
pub fn not_implemented(subcommand: &str) -> Self {
Self::new(
ExitCode::NotImplemented,
"NOT_IMPLEMENTED",
format!("`dbmd {subcommand}` is not implemented yet"),
)
.with_hint("this subcommand is recognized but its body is not implemented in this build")
}
pub fn runtime(message: impl Into<String>) -> Self {
Self::new(ExitCode::Runtime, "RUNTIME_ERROR", message)
}
pub fn to_json(&self) -> serde_json::Value {
let mut obj = serde_json::Map::new();
obj.insert(
"code".to_string(),
serde_json::Value::String(self.code.to_string()),
);
obj.insert(
"message".to_string(),
serde_json::Value::String(self.message.clone()),
);
if let Some(hint) = &self.hint {
obj.insert("hint".to_string(), serde_json::Value::String(hint.clone()));
}
serde_json::json!({ "error": serde_json::Value::Object(obj) })
}
}
impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)?;
if let Some(hint) = &self.hint {
write!(f, "\n hint: {hint}")?;
}
Ok(())
}
}
impl std::error::Error for CliError {}
impl From<dbmd_core::Error> for CliError {
fn from(err: dbmd_core::Error) -> Self {
match err {
dbmd_core::Error::NotAStore(_) => {
CliError::new(ExitCode::NotAStore, "NOT_A_STORE", err.to_string())
.with_hint("run `dbmd` from inside a db.md store, or pass the store path")
}
dbmd_core::Error::Policy { code, message } => {
CliError::new(ExitCode::Policy, code, message)
}
dbmd_core::Error::Store(_) => {
CliError::new(ExitCode::Runtime, "STORE_ERROR", err.to_string())
}
dbmd_core::Error::Parse(_) => {
CliError::new(ExitCode::Runtime, "PARSE_ERROR", err.to_string())
}
dbmd_core::Error::Io(_) => {
CliError::new(ExitCode::Runtime, "IO_ERROR", err.to_string())
}
}
}
}
impl From<std::io::Error> for CliError {
fn from(err: std::io::Error) -> Self {
CliError::new(ExitCode::Runtime, "IO_ERROR", err.to_string())
}
}
pub type CliResult = std::result::Result<(), CliError>;