use core::fmt::{self, Debug};
use std::sync::OnceLock;
use serde::Serialize;
use tracing::{error, info};
static JSON: OnceLock<bool> = OnceLock::new();
pub fn exit_with(out: Output) -> ! {
let status = out.status;
out.print();
if status == Status::Error {
std::process::exit(1);
} else {
std::process::exit(0);
}
}
pub fn json() -> bool {
*JSON.get_or_init(|| false)
}
pub fn set_json(enabled: bool) {
JSON.set(enabled).expect("failed to set JSON mode")
}
pub fn exit_with_unrecoverable_error<T, E: fmt::Display>(err: E) -> T {
Output::error(format!("{err}")).exit()
}
#[derive(Debug)]
pub enum Result {
Json(serde_json::Value),
Text(String),
Value(Box<dyn fmt::Debug + Send + Sync + 'static>),
Nothing,
}
impl fmt::Display for Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Result::Json(v) => write!(f, "{}", serde_json::to_string_pretty(v).unwrap()),
Result::Text(t) => write!(f, "{t}"),
Result::Value(v) => write!(f, "{v:#?}"),
Result::Nothing => Ok(()),
}
}
}
pub struct Output {
pub status: Status,
pub result: Result,
}
impl Output {
pub fn new(status: Status) -> Self {
Output {
status,
result: Result::Nothing,
}
}
pub fn with_success() -> Self {
Output::new(Status::Success)
}
pub fn with_error() -> Self {
Output::new(Status::Error)
}
pub fn with_result<R>(mut self, result: R) -> Self
where
R: Serialize + Debug + Send + Sync + 'static,
{
if json() {
self.result = Result::Json(serde_json::to_value(result).unwrap());
} else {
self.result = Result::Value(Box::new(result));
}
self
}
pub fn with_msg(mut self, msg: impl ToString) -> Self {
self.result = Result::Text(msg.to_string());
self
}
pub fn success<R>(result: R) -> Self
where
R: Serialize + Debug + Send + Sync + 'static,
{
Output::with_success().with_result(result)
}
pub fn error(msg: impl ToString) -> Self {
Output::with_error().with_msg(msg)
}
pub fn success_msg(msg: impl ToString) -> Self {
Output::with_success().with_msg(msg)
}
pub fn print(self) {
if json() {
println!("{}", serde_json::to_string(&self.into_json()).unwrap());
} else {
match self.status {
Status::Success => info!("{}", self.result),
Status::Error => error!("{}", self.result),
}
}
}
pub fn exit(self) -> ! {
exit_with(self);
}
pub fn into_json(self) -> serde_json::Value {
let result = match self.result {
Result::Json(v) => v,
Result::Text(v) => serde_json::Value::String(v),
Result::Value(v) => serde_json::Value::String(format!("{v:#?}")),
Result::Nothing => serde_json::Value::String("no output".to_string()),
};
serde_json::json!({
"status": self.status,
"result": result,
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
pub enum Status {
#[serde(rename(serialize = "success"))]
Success,
#[serde(rename(serialize = "error"))]
Error,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::Success => write!(f, "Success"),
Status::Error => write!(f, "Error"),
}
}
}