use std::collections::{BTreeMap, BTreeSet};
use super::matcher::Matcher;
use super::{
derive, manifests, visibility, BoundaryVerdict, Evidence, PublicNames, Surface, TermRule,
TermScope, Unavailability, Visibility,
};
use crate::{policy, store};
pub struct Derived {
pub rules: Vec<TermRule>,
identities: Vec<String>,
pub sources: usize,
}
pub struct Failed {
pub reason: Unavailability,
pub identity: Option<String>,
pub detail: String,
}
impl Failed {
pub fn new(
reason: Unavailability,
identity: Option<&str>,
detail: impl Into<String>,
) -> Failed {
Failed {
reason,
identity: identity.map(str::to_owned),
detail: detail.into(),
}
}
pub fn verdict(self, evidence: &mut Vec<Evidence>) -> BoundaryVerdict {
let reason = self.reason;
evidence.push(Evidence::Unavailable {
reason,
identity: self.identity,
detail: self.detail,
});
BoundaryVerdict::Unavailable { reason }
}
}
impl Derived {
pub fn empty() -> Derived {
Derived {
rules: Vec::new(),
identities: Vec::new(),
sources: 0,
}
}
pub fn matcher(&self) -> Result<Matcher, Failed> {
Matcher::new(self.rules.clone())
.map_err(|unbuildable| Failed::new(Unavailability::Matcher, None, unbuildable.0))
}
pub fn evidence(&self, surface: Surface, at: String, rule: usize) -> Evidence {
Evidence::Term {
surface,
at,
rule: self.rules[rule].clone(),
identity: self.identities[rule].clone(),
}
}
}
pub fn derive(scope: &TermScope) -> Result<Derived, Failed> {
if matches!(scope, TermScope::Identities(named) if named.is_empty()) {
return Ok(Derived::empty());
}
let registry =
store::load().map_err(|e| Failed::new(Unavailability::Registry, None, e.to_string()))?;
let (file, source) = policy::load(®istry)
.map_err(|e| Failed::new(Unavailability::Rules, None, e.to_string()))?;
let mut visible: BTreeMap<&str, Visibility> = BTreeMap::new();
let mut public = PublicNames::default();
for (key, identity) in ®istry.identities {
let checkout = visibility::checkout_of(®istry, key).unwrap_or(std::path::Path::new(""));
let declared = visibility::declared(&file, &source, key, checkout);
let effective = visibility::recorded(identity, declared).effective();
visible.insert(key.as_str(), effective);
if effective == Visibility::Public {
if let Some(hosted) = store::normalize(key).hosted {
public
.repositories
.insert(super::matcher::term_key(&hosted.name));
public
.owners
.insert(super::matcher::term_key(&hosted.owner));
}
}
}
let selected: Vec<String> = match scope {
TermScope::Registry => visible
.iter()
.filter(|(_, effective)| **effective == Visibility::Private)
.map(|(key, _)| (*key).to_owned())
.collect(),
TermScope::Identities(named) => {
let mut selected = Vec::new();
for name in named {
let key = store::normalize(name).key;
match visible.get(key.as_str()) {
None => {
return Err(Failed::new(
Unavailability::Unregistered,
Some(name),
"not a registered identity",
))
}
Some(Visibility::Public) => {}
Some(_) => {
if !selected.contains(&key) {
selected.push(key);
}
}
}
}
selected
}
};
let mut rules: Vec<TermRule> = Vec::new();
let mut identities: Vec<String> = Vec::new();
let mut seen: BTreeSet<TermRule> = BTreeSet::new();
for key in &selected {
let Some(checkout) = visibility::checkout_of(®istry, key) else {
return Err(Failed::new(
Unavailability::Declarations,
Some(key),
"no registered checkout to read its committed declarations from",
));
};
let term_source = manifests::committed_source(checkout, key)
.map_err(|detail| Failed::new(Unavailability::Declarations, Some(key), detail))?;
let derived = derive::derive(&term_source, &public)
.map_err(|detail| Failed::new(Unavailability::Declarations, Some(key), detail))?;
for rule in derived {
if seen.insert(rule.clone()) {
rules.push(rule);
identities.push(key.clone());
}
}
}
Ok(Derived {
rules,
identities,
sources: selected.len(),
})
}