use std::collections::BTreeMap;
use serde_json::{Map, Value, json};
use super::super::super::{
ADDRESS, ANSWER, CREATE, DELIVER, EFFORT, ENROLL, FAN, FORK, OPS, PREPARE, PRIORITY, PROMPT,
PROPOSALS, RETIRE, UPDATE, answer, create, deliver, effort, enroll, fan, find, fork, ops,
prepare, priority, prompt, proposals, retire, update,
};
use super::{emitted, request};
use crate::envelope;
mod ledger;
use ledger::UNEMITTED;
const BARE: &str = "bare";
const PATH: &str = "path";
const FIELDS: &str = "fields";
fn said(obj: &Map<String, Value>, key: &str) -> Option<String> {
Some(obj.get(key)?.as_str()?.to_owned())
}
fn text(obj: &Map<String, Value>, key: &str) -> String {
obj.get(key)
.and_then(Value::as_str)
.unwrap_or_else(|| panic!("this shape's frame carries no field {key:?}"))
.to_owned()
}
fn from_row(verb: crate::verbs::Verb, obj: &Map<String, Value>) -> Option<Value> {
let raised: Vec<String> = verb
.flags
.iter()
.filter(|flag| obj.get(**flag) == Some(&json!(true)))
.map(|flag| (*flag).to_owned())
.collect();
if obj.len() != verb.params.len() + raised.len() + 1 {
return None;
}
let args = verb
.params
.iter()
.map(|p| text(obj, p))
.chain(raised)
.collect();
Some(verb.envelope(args).expect("the shape's own arity"))
}
fn rebuilt(frame: &Value) -> Option<Value> {
let obj = frame.as_object().expect("a gesture envelope");
let op = text(obj, envelope::OP);
if op == ENROLL.word {
return Some(enroll(
text(obj, envelope::WORKSPACE),
text(obj, "name"),
text(obj, "grade"),
said(obj, ADDRESS),
));
}
if op == PROPOSALS.word {
return Some(proposals(text(obj, envelope::WORKSPACE), said(obj, "id")));
}
if op == ANSWER.word {
return Some(answer(
text(obj, envelope::WORKSPACE),
text(obj, "agent"),
text(obj, "verdict"),
said(obj, crate::verbs::capability::SCOPE),
));
}
if let Some(verb) = find(&op) {
return from_row(verb, obj);
}
match op.as_str() {
PREPARE => match &obj["payload"] {
payload if *payload == json!({ "rung": BARE }) => {
Some(prepare(text(obj, envelope::WORKSPACE), None))
}
payload if payload["rung"] == json!(PATH) => Some(prepare(
text(obj, envelope::WORKSPACE),
payload["dir"].as_str().map(str::to_owned),
)),
_ => None,
},
EFFORT => Some(effort(
text(obj, envelope::WORKSPACE),
text(obj, "role"),
obj["level"].as_str().map(str::to_owned),
)),
PRIORITY => obj["on"]
.as_bool()
.map(|on| priority(text(obj, envelope::WORKSPACE), text(obj, "role"), on)),
OPS => obj["max"].as_u64().map(ops),
FORK => (obj["skills"].as_array().is_some_and(Vec::is_empty)).then(|| {
fork(
text(obj, envelope::WORKSPACE),
text(obj, "parent"),
text(obj, "from"),
text(obj, "role"),
text(obj, "goal"),
)
}),
FAN => obj.contains_key("ball").then(|| {
let staged = crate::reply::start::prepared(obj).expect("a staged body");
let address = staged.workspace.clone();
fan(
&staged,
address,
text(obj, "ball"),
text(obj, "project"),
obj["n"].as_u64().expect("a count"),
)
}),
DELIVER => obj.contains_key("ball").then(|| {
deliver(
text(obj, "ball"),
text(obj, "project"),
text(obj, "handle"),
text(obj, "summary"),
)
}),
RETIRE => obj
.contains_key("ball")
.then(|| retire(text(obj, "ball"), text(obj, "project"), text(obj, "handle"))),
CREATE => (!obj.contains_key(FIELDS)).then(|| {
create(
text(obj, "project"),
text(obj, "name"),
text(obj, "title"),
said(obj, "body"),
)
}),
UPDATE => (!obj.contains_key(FIELDS)).then(|| {
update(
text(obj, "project"),
text(obj, "id"),
text(obj, "name"),
said(obj, "title"),
said(obj, "body"),
said(obj, "note"),
)
}),
PROMPT => obj["seed"].is_null().then(|| {
let staged = crate::reply::start::prepared(obj).expect("a staged body");
let address = staged.workspace.clone();
prompt(&staged, address, text(obj, "goal"), None)
}),
_ => None,
}
}
#[test]
fn what_the_seat_emits_is_what_the_corpus_carries() {
let mut declined: BTreeMap<String, usize> = BTreeMap::new();
for word in emitted() {
for frame in &request(&word).frames {
match rebuilt(frame) {
Some(built) => assert_eq!(&built, frame, "{word}"),
None => *declined.entry(word.clone()).or_default() += 1,
}
}
}
let recorded: BTreeMap<String, usize> = UNEMITTED
.iter()
.map(|(word, n, _)| ((*word).to_owned(), *n))
.collect();
assert_eq!(
declined, recorded,
"the frames this seat cannot compose have moved — amend UNEMITTED with \
the reason, or emit them"
);
}