haematite-cli 0.21.0

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
use std::collections::BTreeMap;
use std::fmt;

use serde::{Deserialize, Serialize};

#[derive(Debug)]
pub struct Error {
    pub code: &'static str,
    pub message: String,
    pub partial: Option<Partial>,
    pub stale_stages: Vec<String>,
}

impl Error {
    pub(super) fn new(code: &'static str, message: &(impl ToString + ?Sized)) -> Self {
        Self {
            code,
            message: message.to_string(),
            partial: None,
            stale_stages: Vec::new(),
        }
    }

    pub(super) fn with_partial(mut self, completed: &[String], published: bool) -> Self {
        if !completed.is_empty() {
            self.partial = Some(Partial {
                name: "bundle_partial",
                completed: completed.to_vec(),
                published,
            });
        }
        self
    }

    pub(super) fn with_stale_stages(mut self, stages: Vec<String>) -> Self {
        self.stale_stages = stages;
        self
    }

    pub(super) fn with_cleanup(mut self, error: &Self) -> Self {
        self.message.push_str("; ");
        self.message.push_str(&error.to_string());
        self
    }
}

#[derive(Debug)]
pub struct Partial {
    pub name: &'static str,
    pub completed: Vec<String>,
    pub published: bool,
}

impl fmt::Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "{}: {}", self.code, self.message)?;
        if let Some(partial) = &self.partial {
            write!(
                formatter,
                "; {}: completed={:?}; published={}",
                partial.name, partial.completed, partial.published
            )?;
        }
        if !self.stale_stages.is_empty() {
            write!(formatter, "; stale_stages={:?}", self.stale_stages)?;
        }
        Ok(())
    }
}

impl std::error::Error for Error {}

#[derive(Debug, Serialize)]
pub struct Report {
    pub directory: String,
    pub files: usize,
    pub already_installed: bool,
    pub stale_stages: Vec<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub(super) struct Manifest {
    pub format: u32,
    pub files: BTreeMap<String, Entry>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub(super) struct Entry {
    pub bytes: u64,
    pub sha256: String,
    pub mode: u32,
}