use std::path::Path;
use loftspec::canonical::Digest;
use loftspec::publish::{LOCK, ReadTree, Root, Unverified, verify};
use loftspec_store::{FileStore, StoreError};
use serde_json::json;
use crate::finish::Finished;
use crate::json;
use crate::out::{errln, outln};
use crate::stop::Stop;
const SCHEMA: &str = "loft-verify/1";
enum Failure {
Stopped(Stop),
Mismatch {
root: Root,
lock: Digest,
tree: Digest,
},
}
impl From<StoreError> for Failure {
fn from(error: StoreError) -> Self {
let kind = if matches!(error, StoreError::Lock(..)) {
"lock"
} else {
"store"
};
Self::Stopped(Stop::one(kind, error.to_string()))
}
}
pub fn run(root: &Path, as_json: bool) -> Finished {
match attempt(root) {
Ok(state) => {
if as_json {
let fields = json!({ "stateRoot": state.to_string() });
json::print(&json::result(SCHEMA, "verified", fields, Vec::new()));
} else {
outln!("verified: state root {state}");
}
Finished::success("verified")
}
Err(Failure::Stopped(stop)) => stop.exit(SCHEMA, as_json),
Err(Failure::Mismatch { root, lock, tree }) => {
if as_json {
let fields = json!({
"root": format!("{}Root", root.name()),
"lock": lock.to_string(),
"tree": tree.to_string(),
});
json::print(&json::result(SCHEMA, "mismatch", fields, Vec::new()));
}
errln!("the {root} differs: {LOCK} has {lock}, the tree has {tree}");
Finished::failure("mismatch")
}
}
}
fn attempt(root: &Path) -> Result<Digest, Failure> {
let store = FileStore::open(root)?;
let files = store.read_files()?;
let tree = ReadTree::read(&files).map_err(|errors| Failure::Stopped(errors.into()))?;
let lock = store.lock()?.ok_or_else(|| {
let message = format!("{LOCK}: there is no lock; `loft publish` writes one");
Failure::Stopped(Stop::one("lock", message))
})?;
verify(&tree, &lock).map_err(|unverified| match unverified {
Unverified::Invalid(errors) => Failure::Stopped(Stop::invalid(&errors)),
Unverified::Mismatch { root, lock, tree } => Failure::Mismatch { root, lock, tree },
})
}