use alloc::vec::Vec;
use vize_l0::String;
use super::{Fingerprint, digest};
use vize_l0::fact::ids::{COMPONENT_USAGES, PROVIDE_INJECT};
const DOMAIN: &[u8] = b"vize.global-summary.v2\0";
const _: () = {
assert!(COMPONENT_USAGES.index() == 4);
assert!(PROVIDE_INJECT.index() == 8);
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum GlobalFacet {
Component = 0,
Provide = 1,
Directive = 2,
}
impl GlobalFacet {
const fn group(self) -> &'static str {
match self {
Self::Component => "global-components",
Self::Provide => "app-provide-inject",
Self::Directive => "dialect-directives",
}
}
const fn schema(self) -> u16 {
1
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GlobalEntry {
pub name: String,
pub contract: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GlobalFacts {
pub components: Vec<GlobalEntry>,
pub provides: Vec<GlobalEntry>,
pub directives: Vec<GlobalEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Fact {
facet: GlobalFacet,
name: String,
contract: String,
}
impl Fact {
fn fingerprint(&self) -> Fingerprint {
digest(
DOMAIN,
self.facet.group(),
self.facet.schema(),
&self.name,
&self.contract,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GlobalSummary {
facts: Vec<Fact>,
}
impl GlobalSummary {
pub fn from_facts(facts: GlobalFacts) -> Result<Self, GlobalError> {
let mut stored = Vec::new();
insert_all(&mut stored, GlobalFacet::Component, facts.components)?;
insert_all(&mut stored, GlobalFacet::Provide, facts.provides)?;
insert_all(&mut stored, GlobalFacet::Directive, facts.directives)?;
stored.sort_unstable_by(|lhs, rhs| {
(lhs.facet, lhs.name.as_str()).cmp(&(rhs.facet, rhs.name.as_str()))
});
Ok(Self { facts: stored })
}
#[must_use]
pub fn fingerprint(&self, facet: GlobalFacet, name: &str) -> Option<Fingerprint> {
let index = self
.facts
.binary_search_by(|fact| (fact.facet, fact.name.as_str()).cmp(&(facet, name)))
.ok()?;
self.facts.get(index).map(|fact| fact.fingerprint())
}
#[must_use]
pub fn invalidated<'a>(&self, resolutions: &'a [GlobalResolution]) -> Vec<&'a str> {
resolutions
.iter()
.filter(|resolution| !resolution.is_fresh(self))
.map(GlobalResolution::file)
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GlobalResolution {
file: String,
resolved: Vec<(GlobalFacet, String, Option<Fingerprint>)>,
}
impl GlobalResolution {
pub fn record(
file: &str,
summary: &GlobalSummary,
resolved: &[(GlobalFacet, &str)],
) -> Result<Self, GlobalError> {
if file.is_empty() {
return Err(GlobalError::EmptyFile);
}
let mut stored = Vec::with_capacity(resolved.len());
for &(facet, name) in resolved {
if stored
.iter()
.any(|(seen_facet, seen_name, _)| *seen_facet == facet && seen_name == name)
{
return Err(GlobalError::Duplicate {
facet,
name: String::from(name),
});
}
stored.push((facet, String::from(name), summary.fingerprint(facet, name)));
}
Ok(Self {
file: String::from(file),
resolved: stored,
})
}
#[must_use]
pub fn file(&self) -> &str {
&self.file
}
#[must_use]
pub fn is_fresh(&self, next: &GlobalSummary) -> bool {
self.resolved
.iter()
.all(|(facet, name, seen)| next.fingerprint(*facet, name) == *seen)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GlobalError {
BadName { facet: GlobalFacet, name: String },
BadContract { facet: GlobalFacet, name: String },
Duplicate { facet: GlobalFacet, name: String },
EmptyFile,
}
fn insert_all(
out: &mut Vec<Fact>,
facet: GlobalFacet,
entries: Vec<GlobalEntry>,
) -> Result<(), GlobalError> {
for entry in entries {
insert(out, facet, entry.name, entry.contract)?;
}
Ok(())
}
fn insert(
out: &mut Vec<Fact>,
facet: GlobalFacet,
name: String,
contract: String,
) -> Result<(), GlobalError> {
if name.is_empty() || name.chars().any(|ch| ch.is_whitespace() || ch == '=') {
return Err(GlobalError::BadName { facet, name });
}
if contract.chars().any(|ch| ch == '\n' || ch == '\r') {
return Err(GlobalError::BadContract { facet, name });
}
if out
.iter()
.any(|fact| fact.facet == facet && fact.name == name)
{
return Err(GlobalError::Duplicate { facet, name });
}
out.push(Fact {
facet,
name,
contract,
});
Ok(())
}