haematite-cli 0.21.1

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Only the session supplies the person; browser authority fields are discarded.
use crate::serve::storage::Failure;
use serde_json::{Value, json};

pub(super) fn parse(bytes: &[u8], store: &str) -> Result<Value, Failure> {
    let body: Value = serde_json::from_slice(bytes).map_err(|_| invalid())?;
    let object = body.as_object().ok_or_else(invalid)?;
    let keys: [&str; 5] = ["text", "command", "config", "session", "cancel"];
    let mut chosen = keys.into_iter().filter(|key| object.contains_key(*key));
    let key = chosen.next().ok_or_else(invalid)?;
    if chosen.next().is_some() {
        return Err(invalid());
    }
    let value = &body[key];
    let valid = match key {
        "text" => value.as_str().is_some_and(|text| !text.trim().is_empty()),
        "cancel" => value == true,
        "command" => {
            value.as_object().is_some_and(|o| o.len() == 2)
                && value["name"].as_str().is_some_and(|name| !name.is_empty())
                && value["input"].is_string()
        }
        "config" => value.as_object().is_some_and(|o| {
            o.is_empty()
                || (o.len() == 2
                    && value["option"]
                        .as_str()
                        .is_some_and(|option| !option.is_empty())
                    && (value["value"].is_string() || value["value"].is_boolean()))
        }),
        "session" => value.as_object().is_some_and(|o| {
            o.len() == 1
                && (value["list"] == true
                    || value["new"] == true
                    || value["open"]
                        .as_str()
                        .is_some_and(super::assistant_wire::identifier))
        }),
        _ => false,
    };
    if !valid {
        return Err(invalid());
    }
    let mut op = json!({key:value});
    if let Some(context) = body.get("context") {
        if key != "text"
            || !context.as_object().is_some_and(|o| {
                o.keys().all(|k| {
                    matches!(
                        k.as_str(),
                        "store" | "branch" | "version" | "screen" | "selection"
                    )
                })
            })
        {
            return Err(invalid());
        }
        if context
            .get("store")
            .is_some_and(|value| value.as_str() != Some(store))
        {
            return Err(Failure::new(
                "assistant_context_mismatch",
                "the shared context must name the store in the URL",
            ));
        }
        if let Some(branch) = context.get("branch")
            && (context["store"] != store
                || !branch.as_str().is_some_and(|branch| {
                    !branch.is_empty() && !branch.chars().any(char::is_control)
                }))
        {
            return Err(Failure::new(
                "assistant_context_invalid",
                "name a branch within the URL's store and include that store in the context",
            ));
        }
        op["context"] = context.clone();
    }
    if object.keys().any(|k| {
        !matches!(
            k.as_str(),
            "text" | "command" | "config" | "session" | "cancel" | "context" | "person"
        )
    }) {
        return Err(invalid());
    }
    Ok(op)
}
fn invalid() -> Failure {
    Failure::new(
        "assistant_op_invalid",
        "send exactly one valid text, command, config, session or cancel operation",
    )
}