use serde_json::Value;
use super::{
ERROR, KIND, OK, Outcome, Read, Reply, agent, balls, board, clients, config, convs, diff,
enrolled, fields, files, governing, help, inbox, lineages, login, ops, providers, queue, rail,
roles, roster, science, search, start, step, steps, stream, transcript,
};
const OUTCOME: &str = "outcome";
const NUDGED: &str = "nudged";
const FLAGGED: &str = "flagged";
const ARMED: &str = "armed";
const ACKED: &str = "acked";
const TRAIL_CLEARED: &str = "trail-cleared";
const ANSWERED: &str = "answered";
const FLOORED: &str = "floored";
const DELIVERED: &str = "delivered";
const RETIRED: &str = "retired";
pub fn read(frame: &Value) -> Read {
match decode(frame) {
Ok(read) => read,
Err(why) => Read::Unreadable(why),
}
}
fn decode(frame: &Value) -> Result<Read, String> {
let obj = frame
.as_object()
.ok_or("reply: not a JSON object".to_owned())?;
let Some(kind) = obj.get(KIND) else {
return refusal(fields::flag(obj, OK)?, fields::text(obj, ERROR));
};
let kind = kind
.as_str()
.ok_or_else(|| format!("reply: non-string field {KIND:?}"))?;
let reply = match kind {
OUTCOME => Reply::Outcome(Outcome {
exit: fields::exit(obj)?,
stdout: fields::text(obj, "stdout")?,
stderr: fields::text(obj, "stderr")?,
}),
NUDGED => Reply::Nudged,
roster::KIND => Reply::Workspaces(roster::workspaces(obj)?),
convs::KIND => Reply::Conversations(fields::rows(obj, convs::row)?),
roles::KIND => Reply::Roles(fields::rows(obj, roles::row)?),
queue::KIND => Reply::Attention(fields::rows(obj, queue::row)?),
FLAGGED => Reply::Flagged,
ANSWERED => Reply::Answered {
tool: fields::text(obj, "tool")?,
tool_use: fields::text(obj, "tool_use")?,
verdict: fields::text(obj, "verdict")?,
advanced: fields::flag(obj, "advanced")?,
},
FLOORED => Reply::Floored {
standing: fields::flag(obj, "standing")?,
},
transcript::KIND => Reply::Transcript(transcript::transcript(obj)?),
steps::KIND => Reply::Steps(steps::steps(obj)?),
files::KIND => Reply::Files(files::files(obj)?),
rail::KIND => Reply::Rail(rail::rail(obj)?),
governing::KIND => Reply::Governing(governing::governing(obj)?),
agent::KIND => Reply::Agent(Box::new(agent::agent(obj)?)),
step::KIND => Reply::Step(Box::new(step::step(obj)?)),
inbox::KIND => Reply::Inbox(fields::rows(obj, inbox::row)?),
help::KIND => Reply::Help(fields::rows(obj, help::row)?),
search::KIND => Reply::Found(search::found(obj)?),
clients::KIND => Reply::Clients(fields::rows(obj, clients::row)?),
config::KIND => Reply::Config(config::config(obj)?),
lineages::KIND => Reply::Lineages(fields::rows(obj, lineages::row)?),
ops::KIND => Reply::Ops(fields::rows(obj, ops::row)?),
ACKED => Reply::Acked,
TRAIL_CLEARED => Reply::TrailCleared,
ARMED => Reply::Armed(fields::flag(obj, ARMED)?),
science::KIND => Reply::Science(fields::rows(obj, science::row)?),
diff::KIND => Reply::Work(fields::rows(obj, diff::diff)?),
balls::KIND => Reply::Balls(fields::rows(obj, balls::row)?),
board::KIND => Reply::Board(board::board(obj)?),
balls::HELD => Reply::WorkspaceBalls(fields::rows(obj, balls::bound)?),
balls::MARKS => Reply::Marks {
branch: balls::marks(obj)?,
},
providers::KIND => Reply::Providers(fields::rows(obj, providers::row)?),
providers::MODELS => Reply::Models(fields::rows(obj, providers::offered)?),
login::KIND => Reply::Login(login::signin(obj)?),
stream::KIND => Reply::Follow(stream::follow(obj)?),
enrolled::KIND => Reply::Enrolled(enrolled::enrolled(obj)?),
start::PREPARED => Reply::Prepared(start::prepared(obj)?),
start::FANNED => Reply::Fanned(fields::rows(obj, start::candidate)?),
DELIVERED => Reply::Delivered {
base: fields::text(obj, "base")?,
target: fields::text(obj, "target")?,
source: fields::opt_text(obj, "source")?,
commit: fields::opt_text(obj, "commit")?,
},
RETIRED => Reply::Retired {
discarded: fields::flag(obj, "discarded")?,
},
start::STARTED => Reply::Started {
conversation: start::started(obj)?,
},
other => {
return Err(format!(
"reply: this seat cannot paint a {other:?} answer — the engine \
speaks a kind this build does not; upgrade the seat"
));
}
};
Ok(Read::Answer(reply))
}
fn refusal(ok: bool, error: Result<String, String>) -> Result<Read, String> {
if ok {
return Err(format!("reply: an answer with no {KIND:?}"));
}
error.map(Read::Refusal)
}