use crate::boundary::Action;
use crate::boundary::config::Write;
use crate::proposals::{Settle, Verdict};
use serde_json::{Map, Value, json};
use super::fields::{bool_of, opt_str_of};
use super::str_of;
use crate::model_pick::{LEVELS, Tuning};
mod file;
pub(super) use file::{decode_file, encode_file};
pub(super) fn decode_action(op: &str, o: &Map<String, Value>) -> Option<Result<Action, String>> {
match op {
"config" => Some(decode_apply(o)),
"marks" => Some(decode_marks(o)),
"model" => Some(decode_pick(o)),
PROPOSAL => Some(decode_settle(o)),
EFFORT => Some(decode_effort(o)),
PRIORITY => Some(decode_priority(o)),
_ => None,
}
}
fn decode_apply(o: &Map<String, Value>) -> Result<Action, String> {
Ok(write(Write::Apply {
file: decode_file(o.get("target").ok_or("config: missing target")?)?,
text: str_of(o, "text")?,
}))
}
fn decode_marks(o: &Map<String, Value>) -> Result<Action, String> {
let branch = str_of(o, "branch")?;
if !crate::world::marks::lawful(&branch) {
return Err(format!("marks: {}", crate::world::marks::REFUSAL));
}
Ok(write(Write::Marks {
workspace: str_of(o, "workspace")?,
branch,
}))
}
pub(super) const EFFORT: &str = "effort";
pub(super) const PRIORITY: &str = "priority";
pub(super) fn encode_tuning(tuning: &Tuning) -> Value {
match tuning {
Tuning::Effort {
workspace,
role,
level,
} => json!({ "op": EFFORT, "workspace": workspace, "role": role,
"level": level.map(|l| l.as_str()) }),
Tuning::Priority {
workspace,
role,
on,
} => json!({ "op": PRIORITY, "workspace": workspace, "role": role, "on": on }),
}
}
fn decode_effort(o: &Map<String, Value>) -> Result<Action, String> {
let level = match opt_str_of(o, "level")? {
None => None,
Some(word) => Some(
crate::model_pick::Effort::parse(&word)
.ok_or_else(|| format!("{EFFORT}: level must be one of {LEVELS}, got {word:?}"))?,
),
};
Ok(write(Write::Tune(Tuning::Effort {
workspace: str_of(o, "workspace")?,
role: str_of(o, "role")?,
level,
})))
}
fn decode_priority(o: &Map<String, Value>) -> Result<Action, String> {
Ok(write(Write::Tune(Tuning::Priority {
workspace: str_of(o, "workspace")?,
role: str_of(o, "role")?,
on: bool_of(o, "on")?,
})))
}
fn decode_pick(o: &Map<String, Value>) -> Result<Action, String> {
Ok(write(Write::Pick {
workspace: str_of(o, "workspace")?,
role: str_of(o, "role")?,
provider: str_of(o, "provider")?,
model: str_of(o, "model")?,
}))
}
pub(super) const PROPOSAL: &str = "proposal";
pub(super) fn encode_settle(settle: &Settle) -> Value {
json!({ "op": PROPOSAL, "workspace": settle.workspace,
"id": settle.id, "verdict": settle.verdict.word() })
}
fn decode_settle(o: &Map<String, Value>) -> Result<Action, String> {
let word = str_of(o, "verdict")?;
let verdict = Verdict::parse(&word)
.ok_or_else(|| format!("{PROPOSAL}: verdict must be one of accept|reject, got {word:?}"))?;
Ok(write(Write::Proposal(Settle {
workspace: str_of(o, "workspace")?,
id: str_of(o, "id")?,
verdict,
})))
}
pub(super) fn encode_write(w: &Write) -> Value {
match w {
Write::Apply { file, text } => {
json!({ "op": "config", "target": encode_file(file), "text": text })
}
Write::Marks { workspace, branch } => {
json!({ "op": "marks", "workspace": workspace, "branch": branch })
}
Write::Pick {
workspace,
role,
provider,
model,
} => json!({ "op": "model", "workspace": workspace,
"role": role, "provider": provider, "model": model }),
Write::Tune(tuning) => encode_tuning(tuning),
Write::Proposal(settle) => encode_settle(settle),
}
}
fn write(w: Write) -> Action {
Action::Config(w)
}
#[cfg(test)]
pub(crate) mod tests;
pub(super) fn encode_read(read: &crate::boundary::config::Read) -> Value {
use crate::boundary::config::Read;
match read {
Read::File { file } => {
json!({ "op": "config", "target": encode_file(file) })
}
Read::Marks { workspace } => json!({ "op": "marks", "workspace": workspace }),
Read::Providers { workspace } => json!({ "op": "providers", "workspace": workspace }),
Read::Roles { workspace } => json!({ "op": "roles", "workspace": workspace }),
Read::Lineages { workspace } => json!({ "op": "lineages", "workspace": workspace }),
Read::Models {
workspace,
provider,
} => json!({ "op": "models", "workspace": workspace, "provider": provider }),
Read::Proposals { workspace, id } => match id {
Some(id) => json!({ "op": PROPOSALS, "workspace": workspace, "id": id }),
None => json!({ "op": PROPOSALS, "workspace": workspace }),
},
}
}
pub(super) const PROPOSALS: &str = "proposals";