use serde_json::{Map, Value, json};
use super::{Attempt, Change, Churn, FileChurn};
use crate::files_view::Preview;
pub(crate) fn reply(attempts: &[Attempt], patch: Option<&Preview>) -> Value {
let mut map = Map::new();
map.insert("ok".to_owned(), json!(true));
map.insert("kind".to_owned(), json!("work-diff"));
map.insert(
"rows".to_owned(),
Value::Array(attempts.iter().map(attempt_row).collect()),
);
if let Some(patch) = patch {
map.insert(
"patch".to_owned(),
crate::files_view::wire::preview_value(patch),
);
}
Value::Object(map)
}
pub(crate) fn attempt_row(attempt: &Attempt) -> Value {
let mut map = Map::new();
map.insert("project".to_owned(), json!(attempt.project));
map.insert("ball_id".to_owned(), json!(attempt.ball_id));
if let Some(handle) = &attempt.handle {
map.insert("handle".to_owned(), json!(handle));
}
if let Some(delivered) = &attempt.delivered {
map.insert("delivered".to_owned(), json!(delivered));
}
match &attempt.change {
Change::Unreadable => {
map.insert("state".to_owned(), json!("unreadable"));
}
Change::Absent {
target,
source,
missing,
} => {
map.insert("state".to_owned(), json!("absent"));
map.insert("target".to_owned(), json!(target));
map.insert("source".to_owned(), json!(source));
map.insert("missing".to_owned(), json!(missing));
}
Change::Diff {
target,
source,
target_oid,
source_oid,
files,
truncated,
} => {
map.insert("state".to_owned(), json!("diff"));
map.insert("target".to_owned(), json!(target));
map.insert("source".to_owned(), json!(source));
map.insert("target_oid".to_owned(), json!(target_oid));
map.insert("source_oid".to_owned(), json!(source_oid));
map.insert(
"files".to_owned(),
Value::Array(files.iter().map(file_row).collect()),
);
map.insert("truncated".to_owned(), json!(truncated));
}
}
Value::Object(map)
}
fn file_row(file: &FileChurn) -> Value {
match &file.churn {
Churn::Text { added, removed } => {
json!({ "path": file.path, "added": added, "removed": removed })
}
Churn::Binary => json!({ "path": file.path, "binary": true }),
}
}
pub(crate) fn attempts_of(obj: &serde_json::Map<String, Value>) -> Result<Vec<Attempt>, String> {
use crate::boundary::codec::fields::list_of;
list_of(obj, "rows", attempt_of)
}
pub(crate) fn attempt_of(v: &Value) -> Result<Attempt, String> {
use crate::boundary::codec::fields::{bool_of, list_of, opt_str_of, str_of, strings_of};
let o = v.as_object().ok_or("attempt: not an object")?;
let change = match str_of(o, "state")?.as_str() {
"unreadable" => Change::Unreadable,
"absent" => Change::Absent {
target: str_of(o, "target")?,
source: str_of(o, "source")?,
missing: strings_of(o, "missing")?,
},
"diff" => Change::Diff {
target: str_of(o, "target")?,
source: str_of(o, "source")?,
target_oid: str_of(o, "target_oid")?,
source_oid: str_of(o, "source_oid")?,
files: list_of(o, "files", file_of)?,
truncated: bool_of(o, "truncated")?,
},
other => return Err(format!("attempt: unknown state {other:?}")),
};
Ok(Attempt {
project: str_of(o, "project")?,
ball_id: str_of(o, "ball_id")?,
handle: opt_str_of(o, "handle")?,
delivered: opt_str_of(o, "delivered")?,
change,
})
}
fn file_of(v: &Value) -> Result<FileChurn, String> {
use crate::boundary::codec::fields::{str_of, u64_of};
let o = v.as_object().ok_or("file churn: not an object")?;
let churn = match o.get("binary") {
Some(_) => Churn::Binary,
None => Churn::Text {
added: u64_of(o, "added")?,
removed: u64_of(o, "removed")?,
},
};
Ok(FileChurn {
path: str_of(o, "path")?,
churn,
})
}