use serde::Serialize;
use serde_json::Value;
pub const API_VERSION: &str = "fez/v1";
pub fn table_data(columns: &[&str], rows: Vec<Value>) -> Value {
let count = rows.len();
serde_json::json!({
"columns": columns,
"rows": rows,
"count": count,
})
}
#[derive(Serialize)]
pub struct Envelope {
#[serde(rename = "apiVersion")]
pub api_version: &'static str,
pub kind: String,
pub host: String,
pub status: Status,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ApiError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<Value>,
}
#[derive(Serialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum Status {
Ok,
Error,
}
#[derive(Serialize)]
pub struct ApiError {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<Value>,
}
impl Envelope {
pub fn ok(kind: &str, host: &str, data: Value) -> Self {
Envelope {
api_version: API_VERSION,
kind: kind.into(),
host: host.into(),
status: Status::Ok,
data: Some(data),
error: None,
hints: None,
}
}
pub fn error(kind: &str, host: &str, err: ApiError) -> Self {
Envelope {
api_version: API_VERSION,
kind: kind.into(),
host: host.into(),
status: Status::Error,
data: None,
error: Some(err),
hints: None,
}
}
pub fn with_hints(mut self, hints: Value) -> Self {
self.hints = Some(hints);
self
}
pub fn to_json_string(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| {
r#"{"apiVersion":"fez/v1","kind":"Error","host":"","status":"error","error":{"code":"internal","message":"envelope serialization failed"}}"#
.to_string()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn ok_envelope_shape() {
let e = Envelope::ok("Sample", "localhost", json!({"k":"v"}));
assert_eq!(
serde_json::to_value(&e).unwrap(),
json!({
"apiVersion":"fez/v1","kind":"Sample","host":"localhost",
"status":"ok","data":{"k":"v"}
})
);
}
#[test]
fn error_envelope_shape() {
let e = Envelope::error(
"Error",
"h1",
ApiError {
code: "not-found".into(),
message: "no unit".into(),
detail: None,
},
);
assert_eq!(
serde_json::to_value(&e).unwrap(),
json!({
"apiVersion":"fez/v1","kind":"Error","host":"h1",
"status":"error","error":{"code":"not-found","message":"no unit"}
})
);
}
#[test]
fn table_data_projects_columns_rows_count() {
let td = table_data(
&["name", "size"],
vec![json!(["bash", 7340032]), json!(["htop", 245760])],
);
assert_eq!(
td,
json!({
"columns": ["name", "size"],
"rows": [["bash", 7340032], ["htop", 245760]],
"count": 2
})
);
assert!(td["rows"][0][1].is_i64() || td["rows"][0][1].is_u64());
}
#[test]
fn table_data_empty_has_zero_count() {
let td = table_data(&["name"], vec![]);
assert_eq!(td, json!({"columns": ["name"], "rows": [], "count": 0}));
}
#[test]
fn ok_envelope_with_hints() {
let e = Envelope::ok(
"ServiceMutation",
"localhost",
json!({"unit": "nginx.service"}),
)
.with_hints(json!({"reverse": "fez services start nginx.service"}));
assert_eq!(
serde_json::to_value(&e).unwrap(),
json!({
"apiVersion":"fez/v1","kind":"ServiceMutation","host":"localhost",
"status":"ok","data":{"unit":"nginx.service"},
"hints":{"reverse":"fez services start nginx.service"}
})
);
}
}