use camino::{Utf8Path, Utf8PathBuf};
use serde::Serialize;
use crate::domain::manifest::DOCS_SCRATCH_VAR;
use crate::domain::paths::{
self, ActivePaths, Paths, UserEnv, docs_scratch_location, recorded_paths, variable,
};
use crate::domain::profile::{DocsRoot, ProfileId};
use crate::domain::version::CanonVersion;
use crate::error::AppError;
use crate::services::verifier;
pub const SCHEMA: &str = "sdd.status/2";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Alignment {
Aligned,
BinaryNewer,
InstanceNewer,
}
#[derive(Debug, Serialize)]
pub struct StatusReport {
pub schema: &'static str,
pub instance: bool,
pub profile: Option<ProfileId>,
pub docs_root: Option<DocsRoot>,
pub docs_scratch: Option<Utf8PathBuf>,
pub docs_scratch_env: Option<String>,
pub canon_version: Option<CanonVersion>,
pub binary_version: CanonVersion,
pub alignment: Option<Alignment>,
pub managed_drift: usize,
pub adopted_drift: usize,
pub failures: usize,
pub ok: Option<bool>,
pub paths: Paths,
}
fn proposals(target: &Utf8Path, env: &UserEnv) -> paths::Proposals {
paths::proposals(env, |relative| target.join(relative).is_dir())
}
fn derived_paths(target: &Utf8Path, env: &UserEnv) -> Paths {
Paths {
user: env.user_paths(),
active: None,
candidates: paths::candidates(),
proposals: proposals(target, env),
}
}
fn absent(target: &Utf8Path) -> StatusReport {
let env = UserEnv::from_process();
StatusReport {
schema: SCHEMA,
paths: derived_paths(target, &env),
instance: false,
profile: None,
docs_root: None,
docs_scratch: None,
docs_scratch_env: variable(DOCS_SCRATCH_VAR),
canon_version: None,
binary_version: CanonVersion::current(),
alignment: None,
managed_drift: 0,
adopted_drift: 0,
failures: 0,
ok: None,
}
}
pub fn status(target: &Utf8Path) -> Result<StatusReport, AppError> {
let manifest = match verifier::read_manifest(target) {
Ok(manifest) => manifest,
Err(AppError::ManifestMissing(_)) => return Ok(absent(target)),
Err(error) => return Err(error),
};
let report = verifier::verify(
target,
&crate::release::embedded::EmbeddedReleaseBundle::new(),
)?;
let binary = CanonVersion::current();
let alignment = match manifest.canon_version.cmp(&binary) {
std::cmp::Ordering::Equal => Alignment::Aligned,
std::cmp::Ordering::Less => Alignment::BinaryNewer,
std::cmp::Ordering::Greater => Alignment::InstanceNewer,
};
let env = UserEnv::from_process();
let mut resolved = derived_paths(target, &env);
resolved.active = Some(ActivePaths {
profile: manifest.profile,
destinations: recorded_paths(manifest.docs_root),
docs_scratch: docs_scratch_location(manifest.docs_scratch.as_deref(), &env),
});
Ok(StatusReport {
schema: SCHEMA,
paths: resolved,
instance: true,
profile: Some(manifest.profile),
docs_root: Some(manifest.docs_root),
docs_scratch: manifest.docs_scratch,
docs_scratch_env: variable(DOCS_SCRATCH_VAR),
canon_version: Some(manifest.canon_version),
binary_version: binary,
alignment: Some(alignment),
managed_drift: report.managed_drift,
adopted_drift: report.adopted_drift,
failures: report.failures,
ok: Some(report.failures == 0),
})
}