Skip to main content

omena_parser/closed_world/
contract.rs

1//! Closed-world contract types: module identity, instance keys, linked-module
2//! facts, reachability, and the sealed bundle exposed to consumers.
3
4use serde::Serialize;
5
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
7#[serde(rename_all = "camelCase")]
8pub struct ModuleIdV0(String);
9
10impl ModuleIdV0 {
11    pub fn new(value: impl Into<String>) -> Self {
12        Self(value.into())
13    }
14
15    pub fn as_str(&self) -> &str {
16        &self.0
17    }
18}
19
20impl From<&str> for ModuleIdV0 {
21    fn from(value: &str) -> Self {
22        Self::new(value)
23    }
24}
25
26impl From<String> for ModuleIdV0 {
27    fn from(value: String) -> Self {
28        Self::new(value)
29    }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
33#[serde(rename_all = "camelCase")]
34pub struct ConfigurationHashV0(String);
35
36impl ConfigurationHashV0 {
37    pub fn new(value: impl Into<String>) -> Self {
38        Self(value.into())
39    }
40
41    pub fn none() -> Self {
42        Self::new("with:none")
43    }
44
45    pub fn as_str(&self) -> &str {
46        &self.0
47    }
48}
49
50impl Default for ConfigurationHashV0 {
51    fn default() -> Self {
52        Self::none()
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
57#[serde(rename_all = "camelCase")]
58pub struct ModuleInstanceKeyV0 {
59    module: ModuleIdV0,
60    configuration: ConfigurationHashV0,
61}
62
63impl ModuleInstanceKeyV0 {
64    pub fn new(module: ModuleIdV0, configuration: ConfigurationHashV0) -> Self {
65        Self {
66            module,
67            configuration,
68        }
69    }
70
71    pub fn unconfigured(module: ModuleIdV0) -> Self {
72        Self::new(module, ConfigurationHashV0::none())
73    }
74
75    pub fn module(&self) -> &ModuleIdV0 {
76        &self.module
77    }
78
79    pub fn configuration(&self) -> &ConfigurationHashV0 {
80        &self.configuration
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
85#[serde(rename_all = "camelCase")]
86pub struct ClosedWorldLinkedModuleV0 {
87    pub instance: ModuleInstanceKeyV0,
88    pub dependencies: Vec<ModuleInstanceKeyV0>,
89    pub class_names: Vec<String>,
90    pub keyframe_names: Vec<String>,
91    pub value_names: Vec<String>,
92    pub custom_property_names: Vec<String>,
93}
94
95impl ClosedWorldLinkedModuleV0 {
96    pub fn new(instance: ModuleInstanceKeyV0) -> Self {
97        Self {
98            instance,
99            dependencies: Vec::new(),
100            class_names: Vec::new(),
101            keyframe_names: Vec::new(),
102            value_names: Vec::new(),
103            custom_property_names: Vec::new(),
104        }
105    }
106
107    pub fn with_dependency(mut self, dependency: ModuleInstanceKeyV0) -> Self {
108        self.dependencies.push(dependency);
109        self
110    }
111
112    pub fn with_class_name(mut self, name: impl Into<String>) -> Self {
113        self.class_names.push(name.into());
114        self
115    }
116
117    pub fn with_keyframe_name(mut self, name: impl Into<String>) -> Self {
118        self.keyframe_names.push(name.into());
119        self
120    }
121
122    pub fn with_value_name(mut self, name: impl Into<String>) -> Self {
123        self.value_names.push(name.into());
124        self
125    }
126
127    pub fn with_custom_property_name(mut self, name: impl Into<String>) -> Self {
128        self.custom_property_names.push(name.into());
129        self
130    }
131}
132
133#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
134#[serde(rename_all = "camelCase")]
135pub struct ClosedWorldSourcePrecisionSummaryV0 {
136    pub exact_source_count: usize,
137    pub conservative_source_count: usize,
138    pub heuristic_source_count: usize,
139    pub unknown_source_count: usize,
140}
141
142impl ClosedWorldSourcePrecisionSummaryV0 {
143    pub(crate) fn merge(&mut self, other: Self) {
144        self.exact_source_count = self
145            .exact_source_count
146            .saturating_add(other.exact_source_count);
147        self.conservative_source_count = self
148            .conservative_source_count
149            .saturating_add(other.conservative_source_count);
150        self.heuristic_source_count = self
151            .heuristic_source_count
152            .saturating_add(other.heuristic_source_count);
153        self.unknown_source_count = self
154            .unknown_source_count
155            .saturating_add(other.unknown_source_count);
156    }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct ClosedWorldModuleMetadataV0 {
161    module_instance: ModuleInstanceKeyV0,
162    interface_hash: Option<String>,
163    source_precision: Option<ClosedWorldSourcePrecisionSummaryV0>,
164}
165
166impl ClosedWorldModuleMetadataV0 {
167    pub fn new(module_instance: ModuleInstanceKeyV0) -> Self {
168        Self {
169            module_instance,
170            interface_hash: None,
171            source_precision: None,
172        }
173    }
174
175    pub fn module_instance(&self) -> &ModuleInstanceKeyV0 {
176        &self.module_instance
177    }
178
179    pub fn with_interface_hash(mut self, interface_hash: impl Into<String>) -> Self {
180        self.interface_hash = Some(interface_hash.into());
181        self
182    }
183
184    pub fn interface_hash(&self) -> Option<&str> {
185        self.interface_hash.as_deref()
186    }
187
188    pub fn with_source_precision(
189        mut self,
190        source_precision: ClosedWorldSourcePrecisionSummaryV0,
191    ) -> Self {
192        self.source_precision = Some(source_precision);
193        self
194    }
195
196    pub fn source_precision(&self) -> Option<ClosedWorldSourcePrecisionSummaryV0> {
197        self.source_precision
198    }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
202#[serde(rename_all = "camelCase", tag = "status")]
203pub enum ClosedWorldInterfaceHashAvailabilityV0 {
204    Known { interface_hash: String },
205    Absent,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
209#[serde(rename_all = "camelCase")]
210pub struct ClosedWorldInterfaceHashEntryV0 {
211    pub module_instance: ModuleInstanceKeyV0,
212    pub availability: ClosedWorldInterfaceHashAvailabilityV0,
213}
214
215#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
216#[serde(rename_all = "camelCase")]
217pub struct ClosedWorldInterfaceHashSetV0 {
218    entries: Vec<ClosedWorldInterfaceHashEntryV0>,
219}
220
221impl ClosedWorldInterfaceHashSetV0 {
222    pub(crate) fn new(entries: Vec<ClosedWorldInterfaceHashEntryV0>) -> Self {
223        Self { entries }
224    }
225
226    pub fn entries(&self) -> &[ClosedWorldInterfaceHashEntryV0] {
227        &self.entries
228    }
229
230    pub fn all_absent(&self) -> bool {
231        self.entries.iter().all(|entry| {
232            matches!(
233                entry.availability,
234                ClosedWorldInterfaceHashAvailabilityV0::Absent
235            )
236        })
237    }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
241#[serde(rename_all = "camelCase")]
242pub struct ModuleQualifiedSymbolSetV0 {
243    module_instance: ModuleInstanceKeyV0,
244    reachable: bool,
245    class_names: Vec<String>,
246    keyframe_names: Vec<String>,
247    value_names: Vec<String>,
248    custom_property_names: Vec<String>,
249}
250
251impl ModuleQualifiedSymbolSetV0 {
252    pub(crate) fn new(
253        module_instance: ModuleInstanceKeyV0,
254        reachable: bool,
255        class_names: Vec<String>,
256        keyframe_names: Vec<String>,
257        value_names: Vec<String>,
258        custom_property_names: Vec<String>,
259    ) -> Self {
260        Self {
261            module_instance,
262            reachable,
263            class_names,
264            keyframe_names,
265            value_names,
266            custom_property_names,
267        }
268    }
269
270    pub fn module_instance(&self) -> &ModuleInstanceKeyV0 {
271        &self.module_instance
272    }
273
274    pub fn is_reachable(&self) -> bool {
275        self.reachable
276    }
277
278    pub fn class_names(&self) -> &[String] {
279        &self.class_names
280    }
281
282    pub fn keyframe_names(&self) -> &[String] {
283        &self.keyframe_names
284    }
285
286    pub fn value_names(&self) -> &[String] {
287        &self.value_names
288    }
289
290    pub fn custom_property_names(&self) -> &[String] {
291        &self.custom_property_names
292    }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
296#[serde(rename_all = "camelCase")]
297pub struct ReachabilityIndexV0 {
298    module_instances: Vec<ModuleInstanceKeyV0>,
299    module_qualified_symbols: Vec<ModuleQualifiedSymbolSetV0>,
300    class_names: Vec<String>,
301    keyframe_names: Vec<String>,
302    value_names: Vec<String>,
303    custom_property_names: Vec<String>,
304}
305
306impl ReachabilityIndexV0 {
307    pub(crate) fn from_parts(
308        module_instances: Vec<ModuleInstanceKeyV0>,
309        module_qualified_symbols: Vec<ModuleQualifiedSymbolSetV0>,
310        class_names: Vec<String>,
311        keyframe_names: Vec<String>,
312        value_names: Vec<String>,
313        custom_property_names: Vec<String>,
314    ) -> Self {
315        Self {
316            module_instances,
317            module_qualified_symbols,
318            class_names,
319            keyframe_names,
320            value_names,
321            custom_property_names,
322        }
323    }
324
325    pub fn module_instances(&self) -> &[ModuleInstanceKeyV0] {
326        &self.module_instances
327    }
328
329    pub fn module_qualified_symbols(&self) -> &[ModuleQualifiedSymbolSetV0] {
330        &self.module_qualified_symbols
331    }
332
333    pub fn symbols_for_module(
334        &self,
335        module_instance: &ModuleInstanceKeyV0,
336    ) -> Option<&ModuleQualifiedSymbolSetV0> {
337        self.module_qualified_symbols
338            .binary_search_by(|entry| entry.module_instance().cmp(module_instance))
339            .ok()
340            .map(|index| &self.module_qualified_symbols[index])
341    }
342
343    pub fn class_names(&self) -> &[String] {
344        &self.class_names
345    }
346
347    pub fn keyframe_names(&self) -> &[String] {
348        &self.keyframe_names
349    }
350
351    pub fn value_names(&self) -> &[String] {
352        &self.value_names
353    }
354
355    pub fn custom_property_names(&self) -> &[String] {
356        &self.custom_property_names
357    }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
361#[serde(rename_all = "camelCase")]
362pub struct ClosedWorldReachabilityBitsetParityReportV0 {
363    pub schema_version: &'static str,
364    pub product: &'static str,
365    pub module_instance_count: usize,
366    pub symbol_name_count: usize,
367    pub module_qualified_symbols: Vec<ModuleQualifiedSymbolSetV0>,
368    pub reachability_equal: bool,
369    pub closure_hash_equal: bool,
370    pub btreeset_closure_hash: String,
371    pub bitset_closure_hash: String,
372}
373
374/// Closed-world bundle constructed from linked module facts.
375///
376/// External callers cannot use field-literal construction:
377///
378/// ```compile_fail
379/// use omena_parser::ClosedWorldBundleV0;
380///
381/// let _bundle = ClosedWorldBundleV0 {
382///     closure_hash: String::new(),
383/// };
384/// ```
385#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
386#[serde(rename_all = "camelCase")]
387pub struct ClosedWorldBundleV0 {
388    entrypoints: Vec<ModuleInstanceKeyV0>,
389    linked_modules: Vec<ModuleInstanceKeyV0>,
390    reachability: ReachabilityIndexV0,
391    closure_hash: String,
392    #[serde(skip_serializing_if = "ClosedWorldInterfaceHashSetV0::all_absent")]
393    interface_hashes: ClosedWorldInterfaceHashSetV0,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    source_precision: Option<ClosedWorldSourcePrecisionSummaryV0>,
396}
397
398impl ClosedWorldBundleV0 {
399    pub(crate) fn seal(
400        entrypoints: Vec<ModuleInstanceKeyV0>,
401        linked_modules: Vec<ModuleInstanceKeyV0>,
402        reachability: ReachabilityIndexV0,
403        closure_hash: String,
404        interface_hashes: ClosedWorldInterfaceHashSetV0,
405        source_precision: Option<ClosedWorldSourcePrecisionSummaryV0>,
406    ) -> Self {
407        Self {
408            entrypoints,
409            linked_modules,
410            reachability,
411            closure_hash,
412            interface_hashes,
413            source_precision,
414        }
415    }
416
417    pub fn entrypoints(&self) -> &[ModuleInstanceKeyV0] {
418        &self.entrypoints
419    }
420
421    pub fn linked_modules(&self) -> &[ModuleInstanceKeyV0] {
422        &self.linked_modules
423    }
424
425    pub fn reachability(&self) -> &ReachabilityIndexV0 {
426        &self.reachability
427    }
428
429    pub fn closure_hash(&self) -> &str {
430        &self.closure_hash
431    }
432
433    pub fn interface_hashes(&self) -> &ClosedWorldInterfaceHashSetV0 {
434        &self.interface_hashes
435    }
436
437    pub fn source_precision(&self) -> Option<ClosedWorldSourcePrecisionSummaryV0> {
438        self.source_precision
439    }
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
443#[serde(rename_all = "camelCase")]
444pub struct OpenWorldSnapshotV0 {
445    reason: String,
446}
447
448impl OpenWorldSnapshotV0 {
449    pub fn new(reason: impl Into<String>) -> Self {
450        Self {
451            reason: reason.into(),
452        }
453    }
454
455    pub fn reason(&self) -> &str {
456        &self.reason
457    }
458}
459
460#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
461#[serde(rename_all = "camelCase")]
462pub enum ClosedWorldBundleBuildErrorV0 {
463    EmptyEntrypoints,
464    MissingEntrypoint {
465        module: ModuleInstanceKeyV0,
466    },
467    MissingDependency {
468        module: ModuleInstanceKeyV0,
469        dependency: ModuleInstanceKeyV0,
470    },
471}