haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use crate::client::Refusal;
use crate::door::http::Response;
use crate::serve::storage::Failure;
use serde::Serialize;
use serde_json::{json, value::RawValue};

/// A store's answer that carries its complete, verified receipt.
#[derive(Serialize)]
pub(super) struct Receipted<'a> {
    pub(super) code: &'a str,
    pub(super) message: &'a str,
    pub(super) path: Option<&'a str>,
    /// The exact bytes the store sent, never parsed and written again, so the
    /// browser can still check them.
    pub(super) receipt: &'a RawValue,
}

/// A store's refusal, with its receipt when the store sent one.
pub(super) fn refused_by_store(refused: &Refusal) -> Response {
    let status = store_status(&refused.code);
    let response = refused.verified.as_ref().map_or_else(
        || {
            Response::json(
                status,
                &json!({"code": refused.code, "message": refused.message, "path": refused.path}),
            )
        },
        |receipt| {
            with_receipt(
                status,
                &Receipted {
                    code: &refused.code,
                    message: &refused.message,
                    path: refused.path.as_deref(),
                    receipt: receipt.raw(),
                },
            )
        },
    );
    retry_after(response, status)
}

fn store_status(code: &str) -> u16 {
    match code {
        "busy"
        | "busy_max_clients"
        | "storage_unavailable"
        | "writer_unavailable"
        | "lys_could_not_be_asked"
        | "lys_keys_pending"
        | "busy_pending_signins" => 503,
        "permission_refused" | "held_for_approval" | "origin_refused" => 403,
        code if code.ends_with("_access_refused") => 403,
        "branch_missing"
        | "branch_unknown"
        | "collection_missing"
        | "document_missing"
        | "store_unknown"
        | "route_unknown"
        | "version_missing"
        | "file_unknown"
        | "relation_missing"
        | "counts_branch_missing" => 404,
        _ => 400,
    }
}

fn retry_after(response: Response, status: u16) -> Response {
    if status == 503 {
        response.with("Retry-After", "1")
    } else {
        response
    }
}

pub(super) fn with_receipt(status: u16, body: &Receipted<'_>) -> Response {
    serde_json::to_vec(body).map_or_else(
        |error| {
            let message = format!(
                "the store answered {}: {}; its receipt could not be written: {error}",
                body.code, body.message
            );
            Response::json(
                502,
                &json!({"code": "receipt_output_failed", "message": message}),
            )
        },
        |bytes| Response::new(status, "application/json", bytes),
    )
}

pub(in crate::door) fn refusal(failure: Failure) -> Response {
    let Failure { code, message, .. } = failure;
    let status = match code.as_str() {
        "session_unknown" | "session_expired" | "sign_in_state_unknown" => 401,
        "origin_refused" => 403,
        "route_unknown" | "store_unknown" => 404,
        "method_refused" => 405,
        _ => store_status(&code),
    };
    retry_after(
        Response::json(status, &json!({"code": code, "message": message})),
        status,
    )
}