use crate::opslog::Origin;
use crate::projects::join::JoinState;
use crate::start::{BallSpec, Payload, Prepared};
use serde_json::{Map, Value, json};
use super::{opt_path_of, opt_str_of, path_of, str_of, strings_of};
pub(super) fn encode_payload(payload: &Payload) -> Value {
match payload {
Payload::Bare => json!({ "rung": "bare" }),
Payload::Path { dir } => json!({ "rung": "path", "dir": dir.to_string_lossy() }),
Payload::Ball { project, ball } => json!({
"rung": "ball",
"project": project,
"ball": encode_ball(ball),
}),
}
}
pub(super) fn decode_payload(v: &Value) -> Result<Payload, String> {
let obj = v.as_object().ok_or("payload: not an object")?;
match str_of(obj, "rung")?.as_str() {
"bare" => Ok(Payload::Bare),
"path" => Ok(Payload::Path {
dir: path_of(obj, "dir")?,
}),
"ball" => Ok(Payload::Ball {
project: str_of(obj, "project")?,
ball: decode_ball(obj.get("ball").ok_or("payload: missing ball")?)?,
}),
other => Err(format!("payload: unknown rung {other:?}")),
}
}
fn encode_ball(ball: &BallSpec) -> Value {
match ball {
BallSpec::Existing {
id,
title,
body,
join,
tags,
} => json!({ "id": id, "title": title, "body": body,
"join": join_token(*join), "tags": tags }),
BallSpec::New { title, body } => json!({ "title": title, "body": body }),
}
}
fn decode_ball(v: &Value) -> Result<BallSpec, String> {
let obj = v.as_object().ok_or("ball: not an object")?;
let title = str_of(obj, "title")?;
let body = str_of(obj, "body")?;
match obj.get("id") {
Some(id) => Ok(BallSpec::Existing {
id: id.as_str().ok_or("ball: id not a string")?.to_owned(),
title,
body,
join: parse_join(&str_of(obj, "join")?)?,
tags: strings_of(obj, "tags")?,
}),
None => Ok(BallSpec::New { title, body }),
}
}
pub(crate) fn encode_prepared(p: &Prepared) -> Value {
json!({
"workspace": p.workspace,
"binding": p.binding.as_ref().map(|b| b.to_string_lossy()),
"lineage": p.lineage,
"goal": p.goal,
"origin": origin_token(p.origin),
})
}
pub(crate) fn encode_start(action: &crate::boundary::Action) -> Value {
use crate::boundary::Action;
match action {
Action::Prepare { workspace, payload } => {
json!({ "op": "prepare", "workspace": workspace,
"payload": encode_payload(payload) })
}
Action::Prompt {
prepared,
goal,
seed,
} => json!({ "op": "prompt", "prepared": encode_prepared(prepared),
"goal": goal, "seed": seed }),
_ => Value::Null,
}
}
pub(crate) fn decode_prepared(v: &Value) -> Result<Prepared, String> {
let obj = v.as_object().ok_or("prepared: not an object")?;
Ok(Prepared {
workspace: str_of(obj, "workspace")?,
binding: opt_path_of(obj, "binding")?,
lineage: opt_str_of(obj, "lineage")?,
goal: str_of(obj, "goal")?,
origin: parse_origin(&str_of(obj, "origin")?)?,
})
}
pub(crate) fn join_token(state: JoinState) -> &'static str {
match state {
JoinState::ReadyStartable => "ready",
JoinState::Blocked => "blocked",
JoinState::Bound => "bound",
JoinState::ClaimedElsewhere => "claimed-elsewhere",
JoinState::Delivered => "delivered",
JoinState::UnassignedWorkspace => "unassigned-workspace",
JoinState::OrphanedProject => "orphaned-project",
}
}
pub(crate) fn parse_join(token: &str) -> Result<JoinState, String> {
match token {
"ready" => Ok(JoinState::ReadyStartable),
"blocked" => Ok(JoinState::Blocked),
"bound" => Ok(JoinState::Bound),
"claimed-elsewhere" => Ok(JoinState::ClaimedElsewhere),
"delivered" => Ok(JoinState::Delivered),
"unassigned-workspace" => Ok(JoinState::UnassignedWorkspace),
"orphaned-project" => Ok(JoinState::OrphanedProject),
other => Err(format!("unknown join state {other:?}")),
}
}
pub(crate) fn origin_token(origin: Origin) -> &'static str {
match origin {
Origin::Balls => "balls",
Origin::Conversation => "conversation",
Origin::World => "world",
}
}
pub(crate) fn parse_origin(token: &str) -> Result<Origin, String> {
match token {
"balls" => Ok(Origin::Balls),
"conversation" => Ok(Origin::Conversation),
"world" => Ok(Origin::World),
other => Err(format!("unknown origin {other:?}")),
}
}
pub(super) fn opt_field(map: &mut Map<String, Value>, key: &str, v: Option<&String>) {
if let Some(s) = v {
map.insert(key.to_owned(), Value::String(s.clone()));
}
}