1use alloc::vec::Vec;
9
10use vize_l0::String;
11
12use super::{Fingerprint, digest};
13use vize_l0::fact::ids::{COMPONENT_USAGES, PROVIDE_INJECT};
14
15const DOMAIN: &[u8] = b"vize.global-summary.v2\0";
16
17const _: () = {
18 assert!(COMPONENT_USAGES.index() == 4);
19 assert!(PROVIDE_INJECT.index() == 8);
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[repr(u8)]
25pub enum GlobalFacet {
26 Component = 0,
28 Provide = 1,
30 Directive = 2,
32}
33
34impl GlobalFacet {
35 const fn group(self) -> &'static str {
36 match self {
37 Self::Component => "global-components",
38 Self::Provide => "app-provide-inject",
39 Self::Directive => "dialect-directives",
40 }
41 }
42
43 const fn schema(self) -> u16 {
44 1
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct GlobalEntry {
51 pub name: String,
53 pub contract: String,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct GlobalFacts {
60 pub components: Vec<GlobalEntry>,
62 pub provides: Vec<GlobalEntry>,
64 pub directives: Vec<GlobalEntry>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69struct Fact {
70 facet: GlobalFacet,
71 name: String,
72 contract: String,
73}
74
75impl Fact {
76 fn fingerprint(&self) -> Fingerprint {
77 digest(
78 DOMAIN,
79 self.facet.group(),
80 self.facet.schema(),
81 &self.name,
82 &self.contract,
83 )
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct GlobalSummary {
90 facts: Vec<Fact>,
91}
92
93impl GlobalSummary {
94 pub fn from_facts(facts: GlobalFacts) -> Result<Self, GlobalError> {
101 let mut stored = Vec::new();
102 insert_all(&mut stored, GlobalFacet::Component, facts.components)?;
103 insert_all(&mut stored, GlobalFacet::Provide, facts.provides)?;
104 insert_all(&mut stored, GlobalFacet::Directive, facts.directives)?;
105 stored.sort_unstable_by(|lhs, rhs| {
106 (lhs.facet, lhs.name.as_str()).cmp(&(rhs.facet, rhs.name.as_str()))
107 });
108 Ok(Self { facts: stored })
109 }
110
111 #[must_use]
113 pub fn fingerprint(&self, facet: GlobalFacet, name: &str) -> Option<Fingerprint> {
114 let index = self
115 .facts
116 .binary_search_by(|fact| (fact.facet, fact.name.as_str()).cmp(&(facet, name)))
117 .ok()?;
118 self.facts.get(index).map(|fact| fact.fingerprint())
119 }
120
121 #[must_use]
124 pub fn invalidated<'a>(&self, resolutions: &'a [GlobalResolution]) -> Vec<&'a str> {
125 resolutions
126 .iter()
127 .filter(|resolution| !resolution.is_fresh(self))
128 .map(GlobalResolution::file)
129 .collect()
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct GlobalResolution {
140 file: String,
141 resolved: Vec<(GlobalFacet, String, Option<Fingerprint>)>,
142}
143
144impl GlobalResolution {
145 pub fn record(
151 file: &str,
152 summary: &GlobalSummary,
153 resolved: &[(GlobalFacet, &str)],
154 ) -> Result<Self, GlobalError> {
155 if file.is_empty() {
156 return Err(GlobalError::EmptyFile);
157 }
158 let mut stored = Vec::with_capacity(resolved.len());
159 for &(facet, name) in resolved {
160 if stored
161 .iter()
162 .any(|(seen_facet, seen_name, _)| *seen_facet == facet && seen_name == name)
163 {
164 return Err(GlobalError::Duplicate {
165 facet,
166 name: String::from(name),
167 });
168 }
169 stored.push((facet, String::from(name), summary.fingerprint(facet, name)));
170 }
171 Ok(Self {
172 file: String::from(file),
173 resolved: stored,
174 })
175 }
176
177 #[must_use]
179 pub fn file(&self) -> &str {
180 &self.file
181 }
182
183 #[must_use]
185 pub fn is_fresh(&self, next: &GlobalSummary) -> bool {
186 self.resolved
187 .iter()
188 .all(|(facet, name, seen)| next.fingerprint(*facet, name) == *seen)
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum GlobalError {
195 BadName { facet: GlobalFacet, name: String },
197 BadContract { facet: GlobalFacet, name: String },
199 Duplicate { facet: GlobalFacet, name: String },
201 EmptyFile,
203}
204
205fn insert_all(
206 out: &mut Vec<Fact>,
207 facet: GlobalFacet,
208 entries: Vec<GlobalEntry>,
209) -> Result<(), GlobalError> {
210 for entry in entries {
211 insert(out, facet, entry.name, entry.contract)?;
212 }
213 Ok(())
214}
215
216fn insert(
217 out: &mut Vec<Fact>,
218 facet: GlobalFacet,
219 name: String,
220 contract: String,
221) -> Result<(), GlobalError> {
222 if name.is_empty() || name.chars().any(|ch| ch.is_whitespace() || ch == '=') {
223 return Err(GlobalError::BadName { facet, name });
224 }
225 if contract.chars().any(|ch| ch == '\n' || ch == '\r') {
226 return Err(GlobalError::BadContract { facet, name });
227 }
228 if out
229 .iter()
230 .any(|fact| fact.facet == facet && fact.name == name)
231 {
232 return Err(GlobalError::Duplicate { facet, name });
233 }
234 out.push(Fact {
235 facet,
236 name,
237 contract,
238 });
239 Ok(())
240}