use serde_json::{Map, Value, json};
use crate::actions::verbs::{Verb, edit};
use crate::boundary::Action;
use super::fields::{bool_of, i64_of, list_of, opt};
use super::start::opt_field;
use super::{obj, opt_str_of, str_of};
pub(super) fn encode(verb: &Verb) -> Value {
match verb {
Verb::Close { project, id, name } => ball("close", project, id, name),
Verb::Assign { project, id, name } => ball("assign", project, id, name),
Verb::Release { project, id, name } => ball("release", project, id, name),
Verb::Create {
project,
name,
fields,
} => create(project, name, fields),
Verb::Update {
project,
id,
name,
fields,
} => update(project, id, name, fields),
}
}
fn ball(op: &str, project: &str, id: &str, name: &str) -> Value {
json!({ "op": op, "project": project, "id": id, "name": name })
}
fn create(project: &str, name: &str, fields: &edit::Create) -> Value {
let mut map = obj(&[("op", "create"), ("title", &fields.title), ("name", name)]);
map.insert("project".to_owned(), Value::String(project.to_owned()));
opt_field(&mut map, "body", fields.body.as_ref());
encode_fields(&mut map, &fields.fields);
Value::Object(map)
}
fn update(project: &str, id: &str, name: &str, fields: &edit::Update) -> Value {
let mut map = obj(&[("op", "update"), ("id", id), ("name", name)]);
map.insert("project".to_owned(), Value::String(project.to_owned()));
for (key, value) in
["title", "body", "note"]
.into_iter()
.zip([&fields.title, &fields.body, &fields.note])
{
opt_field(&mut map, key, value.as_ref());
}
encode_fields(&mut map, &fields.fields);
Value::Object(map)
}
fn encode_fields(map: &mut Map<String, Value>, fields: &[edit::Field]) {
if !fields.is_empty() {
let rows: Vec<Value> = fields.iter().map(encode_field).collect();
map.insert("fields".to_owned(), Value::Array(rows));
}
}
fn encode_field(field: &edit::Field) -> Value {
match field {
edit::Field::Priority(n) => json!({ "field": "priority", "value": n }),
edit::Field::Parent(id) => json!({ "field": "parent", "value": id }),
edit::Field::Tag { tag, on } => json!({ "field": "tag", "value": tag, "on": on }),
edit::Field::Needs { edge, on } => json!({ "field": "needs", "value": edge, "on": on }),
}
}
fn decode_fields(o: &Map<String, Value>) -> Result<Vec<edit::Field>, String> {
if o.contains_key("fields") {
list_of(o, "fields", decode_field)
} else {
Ok(Vec::new())
}
}
fn decode_field(v: &Value) -> Result<edit::Field, String> {
let o = v
.as_object()
.ok_or_else(|| "a ball field is an object".to_owned())?;
Ok(match str_of(o, "field")?.as_str() {
"priority" => edit::Field::Priority(opt(o, "value", i64_of)?),
"parent" => edit::Field::Parent(opt(o, "value", str_of)?),
"tag" => edit::Field::Tag {
tag: str_of(o, "value")?,
on: bool_of(o, "on")?,
},
"needs" => edit::Field::Needs {
edge: str_of(o, "value")?,
on: bool_of(o, "on")?,
},
other => return Err(format!("unknown ball field {other:?}")),
})
}
pub(super) fn decode(op: &str, o: &Map<String, Value>) -> Result<Action, String> {
let project = str_of(o, "project")?;
Ok(Action::Ball(match op {
"close" => Verb::Close {
project,
id: str_of(o, "id")?,
name: str_of(o, "name")?,
},
"assign" => Verb::Assign {
project,
id: str_of(o, "id")?,
name: str_of(o, "name")?,
},
"release" => Verb::Release {
project,
id: str_of(o, "id")?,
name: str_of(o, "name")?,
},
"create" => Verb::Create {
project,
name: str_of(o, "name")?,
fields: edit::Create {
title: str_of(o, "title")?,
body: opt_str_of(o, "body")?,
fields: decode_fields(o)?,
},
},
_ => Verb::Update {
project,
id: str_of(o, "id")?,
name: str_of(o, "name")?,
fields: edit::Update {
title: opt_str_of(o, "title")?,
body: opt_str_of(o, "body")?,
note: opt_str_of(o, "note")?,
fields: decode_fields(o)?,
},
},
}))
}