use std::path::Path;
use okf_core::{Bundle, TrustTier};
use serde::Serialize;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum InspectError {
#[error("`{path}` is not a readable OKF bundle: {detail}")]
Unreadable {
path: String,
detail: String,
},
}
fn load(root: &Path) -> Result<Bundle, InspectError> {
Bundle::load(root).map_err(|e| InspectError::Unreadable {
path: root.display().to_string(),
detail: e.to_string(),
})
}
#[derive(Debug, Clone, Serialize)]
pub struct ConceptTrust {
pub id: String,
pub tier: &'static str,
pub status: String,
pub verified_by: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct TrustSummary {
pub root: String,
pub okf_version: Option<String>,
pub total: usize,
pub human_reviewed: usize,
pub machine_confirmed: usize,
pub unverified: usize,
pub concepts: Vec<ConceptTrust>,
}
pub fn trust_summary(root: &Path) -> Result<TrustSummary, InspectError> {
Ok(summarise_trust(&load(root)?, &root.display().to_string()))
}
#[must_use]
pub fn summarise_trust(bundle: &Bundle, root: &str) -> TrustSummary {
let mut summary = TrustSummary {
root: root.to_owned(),
okf_version: bundle.okf_version().map(ToOwned::to_owned),
total: bundle.concepts().len(),
human_reviewed: 0,
machine_confirmed: 0,
unverified: 0,
concepts: Vec::with_capacity(bundle.concepts().len()),
};
for concept in bundle.concepts() {
let tier = concept.trust_tier();
match tier {
TrustTier::HumanReviewed => summary.human_reviewed += 1,
TrustTier::MachineConfirmed => summary.machine_confirmed += 1,
TrustTier::Unverified => summary.unverified += 1,
}
summary.concepts.push(ConceptTrust {
id: concept.id.to_string(),
tier: tier.as_str(),
status: concept.status().to_string(),
verified_by: concept
.document
.frontmatter
.verified()
.into_iter()
.filter_map(|v| v.by.map(|by| by.as_str().to_owned()))
.collect(),
});
}
summary
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct BrokenLink {
pub from: String,
pub target: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct LinkReport {
pub root: String,
pub concepts: usize,
pub links: usize,
pub broken: Vec<BrokenLink>,
}
impl LinkReport {
#[must_use]
pub const fn is_clean(&self) -> bool {
self.broken.is_empty()
}
}
pub fn link_report(root: &Path) -> Result<LinkReport, InspectError> {
let bundle = load(root)?;
let links = bundle
.concepts()
.iter()
.map(|c| bundle.links_from(&c.id).len())
.sum();
Ok(LinkReport {
root: root.display().to_string(),
concepts: bundle.concepts().len(),
links,
broken: bundle
.broken_links()
.into_iter()
.map(|(from, target)| BrokenLink {
from: from.to_string(),
target,
})
.collect(),
})
}
#[derive(Debug, Clone, Serialize)]
pub struct TrustMove {
pub id: String,
pub tier: Option<(String, String)>,
pub status: Option<(String, String)>,
}
#[derive(Debug, Clone, Serialize)]
pub struct DiffReport {
pub before: String,
pub after: String,
pub added: Vec<String>,
pub removed: Vec<String>,
pub renamed: Vec<(String, String)>,
pub content_changed: Vec<String>,
pub frontmatter_changed: Vec<String>,
pub trust_changed: Vec<TrustMove>,
pub links_broken: Vec<(String, String)>,
pub links_mended: Vec<(String, String)>,
}
impl DiffReport {
#[must_use]
pub fn is_unchanged(&self) -> bool {
self.added.is_empty()
&& self.removed.is_empty()
&& self.renamed.is_empty()
&& self.content_changed.is_empty()
&& self.frontmatter_changed.is_empty()
&& self.trust_changed.is_empty()
&& self.links_broken.is_empty()
&& self.links_mended.is_empty()
}
}
pub fn diff_report(before: &Path, after: &Path) -> Result<DiffReport, InspectError> {
let a = load(before)?;
let b = load(after)?;
let d = okf_core::bundle_diff(&a, &b);
let ids = |v: Vec<okf_core::ConceptId>| v.iter().map(ToString::to_string).collect::<Vec<_>>();
let pairs = |v: Vec<(okf_core::ConceptId, String)>| {
v.into_iter()
.map(|(id, t)| (id.to_string(), t))
.collect::<Vec<_>>()
};
Ok(DiffReport {
before: before.display().to_string(),
after: after.display().to_string(),
added: ids(d.added),
removed: ids(d.removed),
renamed: d
.renamed
.into_iter()
.map(|r| (r.from.to_string(), r.to.to_string()))
.collect(),
content_changed: ids(d.content),
frontmatter_changed: d.frontmatter.iter().map(|c| c.id.to_string()).collect(),
trust_changed: d
.trust
.into_iter()
.map(|t| TrustMove {
id: t.id.to_string(),
tier: t
.tier
.map(|(a, b)| (a.as_str().to_owned(), b.as_str().to_owned())),
status: t.status.map(|(a, b)| (a.to_string(), b.to_string())),
})
.collect(),
links_broken: pairs(d.broken_links),
links_mended: pairs(d.mended_links),
})
}