use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandOutcome {
pub command: String,
pub ok: bool,
pub data: Value,
#[serde(default)]
pub human: String,
}
impl CommandOutcome {
pub fn ok(command: impl Into<String>, data: Value, human: impl Into<String>) -> Self {
Self { command: command.into(), ok: true, data, human: human.into() }
}
pub fn fail(command: impl Into<String>, why: impl Into<String>) -> Self {
Self { command: command.into(), ok: false, data: Value::Null, human: why.into() }
}
pub fn print(&self, json: bool) {
if json {
println!("{}", serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".into()));
} else if !self.human.is_empty() {
println!("{}", self.human);
}
}
pub fn is_sannr(&self) -> bool {
self.ok && !data_is_empty(&self.data) && !has_error_marker(&self.human)
}
}
fn data_is_empty(v: &Value) -> bool {
match v {
Value::Null => true,
Value::Array(a) => a.is_empty(),
Value::Object(o) => o.is_empty(),
Value::String(s) => s.trim().is_empty(),
_ => false, }
}
fn has_error_marker(s: &str) -> bool {
const MARKERS: &[&str] =
&["is not served", "Failed to load", "unavailable", "RPC: status:", "panicked"];
MARKERS.iter().any(|m| s.contains(m))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn ok_outcome_with_real_data_is_sannr() {
let o = CommandOutcome::ok("bench history", json!([{"repo":"holger","ops":7.0}]), "1 run");
assert!(o.is_sannr());
let mut buf = serde_json::to_value(&o).unwrap();
assert_eq!(buf["ok"], json!(true));
assert_eq!(buf["command"], json!("bench history"));
assert!(buf["data"].as_array().unwrap().len() == 1);
buf["ok"] = json!(false); assert_eq!(buf["ok"], json!(false));
}
#[test]
fn empty_or_failed_or_errored_is_not_sane() {
assert!(!CommandOutcome::ok("x", json!([]), "no rows").is_sannr());
assert!(!CommandOutcome::ok("x", json!({}), "").is_sannr());
assert!(!CommandOutcome::ok("x", Value::Null, "").is_sannr());
assert!(!CommandOutcome::fail("x", "workspace `` is not served").is_sannr());
assert!(!CommandOutcome::ok("x", json!([1]), "RPC: status: unavailable").is_sannr());
assert!(CommandOutcome::ok("x", json!(0), "zero is a value").is_sannr());
}
}