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 std::collections::BTreeMap;
5
6use omena_syntax::ident::AuthoredPropertyTextV0;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct ModuleIdV0(String);
12
13impl ModuleIdV0 {
14    pub fn new(value: impl Into<String>) -> Self {
15        Self(value.into())
16    }
17
18    pub fn as_str(&self) -> &str {
19        &self.0
20    }
21}
22
23impl From<&str> for ModuleIdV0 {
24    fn from(value: &str) -> Self {
25        Self::new(value)
26    }
27}
28
29impl From<String> for ModuleIdV0 {
30    fn from(value: String) -> Self {
31        Self::new(value)
32    }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
36#[serde(rename_all = "camelCase")]
37pub struct ConfigurationHashV0(String);
38
39impl ConfigurationHashV0 {
40    pub fn new(value: impl Into<String>) -> Self {
41        Self(value.into())
42    }
43
44    pub fn none() -> Self {
45        Self::new("with:none")
46    }
47
48    pub fn as_str(&self) -> &str {
49        &self.0
50    }
51}
52
53impl Default for ConfigurationHashV0 {
54    fn default() -> Self {
55        Self::none()
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
60#[serde(rename_all = "camelCase")]
61pub struct ModuleInstanceKeyV0 {
62    module: ModuleIdV0,
63    configuration: ConfigurationHashV0,
64}
65
66impl ModuleInstanceKeyV0 {
67    pub fn new(module: ModuleIdV0, configuration: ConfigurationHashV0) -> Self {
68        Self {
69            module,
70            configuration,
71        }
72    }
73
74    pub fn unconfigured(module: ModuleIdV0) -> Self {
75        Self::new(module, ConfigurationHashV0::none())
76    }
77
78    pub fn module(&self) -> &ModuleIdV0 {
79        &self.module
80    }
81
82    pub fn configuration(&self) -> &ConfigurationHashV0 {
83        &self.configuration
84    }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
88#[serde(rename_all = "camelCase")]
89pub struct ClosedWorldComposesEdgeV0 {
90    pub from_module: ModuleInstanceKeyV0,
91    pub from_symbol: String,
92    pub to_module: ModuleInstanceKeyV0,
93    pub to_symbol: String,
94}
95
96#[derive(Debug, Clone, Serialize)]
97#[serde(rename_all = "camelCase")]
98pub struct ClosedWorldLinkedModuleV0 {
99    pub instance: ModuleInstanceKeyV0,
100    pub dependencies: Vec<ModuleInstanceKeyV0>,
101    pub composes_edges: Vec<ClosedWorldComposesEdgeV0>,
102    pub composes_edge_observation_count: usize,
103    pub class_names: Vec<String>,
104    pub keyframe_names: Vec<String>,
105    pub value_names: Vec<String>,
106    pub custom_property_names: Vec<AuthoredPropertyTextV0>,
107}
108
109impl PartialEq for ClosedWorldLinkedModuleV0 {
110    fn eq(&self, other: &Self) -> bool {
111        self.instance == other.instance
112            && self.dependencies == other.dependencies
113            && self.composes_edges == other.composes_edges
114            && self.composes_edge_observation_count == other.composes_edge_observation_count
115            && self.class_names == other.class_names
116            && self.keyframe_names == other.keyframe_names
117            && self.value_names == other.value_names
118            && authored_custom_property_sequences_same(
119                &self.custom_property_names,
120                &other.custom_property_names,
121            )
122    }
123}
124
125impl Eq for ClosedWorldLinkedModuleV0 {}
126
127impl ClosedWorldLinkedModuleV0 {
128    pub fn new(instance: ModuleInstanceKeyV0) -> Self {
129        Self {
130            instance,
131            dependencies: Vec::new(),
132            composes_edges: Vec::new(),
133            composes_edge_observation_count: 0,
134            class_names: Vec::new(),
135            keyframe_names: Vec::new(),
136            value_names: Vec::new(),
137            custom_property_names: Vec::new(),
138        }
139    }
140
141    pub fn with_dependency(mut self, dependency: ModuleInstanceKeyV0) -> Self {
142        self.dependencies.push(dependency);
143        self
144    }
145
146    pub fn with_composes_edge(mut self, edge: ClosedWorldComposesEdgeV0) -> Self {
147        self.composes_edges.push(edge);
148        self.composes_edge_observation_count =
149            self.composes_edge_observation_count.saturating_add(1);
150        self
151    }
152
153    pub fn with_class_name(mut self, name: impl Into<String>) -> Self {
154        self.class_names.push(name.into());
155        self
156    }
157
158    pub fn with_keyframe_name(mut self, name: impl Into<String>) -> Self {
159        self.keyframe_names.push(name.into());
160        self
161    }
162
163    pub fn with_value_name(mut self, name: impl Into<String>) -> Self {
164        self.value_names.push(name.into());
165        self
166    }
167
168    pub fn with_custom_property_name(mut self, name: AuthoredPropertyTextV0) -> Self {
169        self.custom_property_names.push(name);
170        self
171    }
172}
173
174#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
175#[serde(rename_all = "camelCase")]
176pub struct ClosedWorldSourcePrecisionSummaryV0 {
177    pub exact_source_count: usize,
178    pub conservative_source_count: usize,
179    pub heuristic_source_count: usize,
180    pub unknown_source_count: usize,
181}
182
183impl ClosedWorldSourcePrecisionSummaryV0 {
184    pub(crate) fn merge(&mut self, other: Self) {
185        self.exact_source_count = self
186            .exact_source_count
187            .saturating_add(other.exact_source_count);
188        self.conservative_source_count = self
189            .conservative_source_count
190            .saturating_add(other.conservative_source_count);
191        self.heuristic_source_count = self
192            .heuristic_source_count
193            .saturating_add(other.heuristic_source_count);
194        self.unknown_source_count = self
195            .unknown_source_count
196            .saturating_add(other.unknown_source_count);
197    }
198}
199
200#[non_exhaustive]
201#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
202#[serde(rename_all = "camelCase")]
203pub enum ClosedWorldModuleReachabilityEvidenceV0 {
204    Supplied,
205    #[default]
206    ModuleReachabilityInputAbsent,
207}
208
209#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
210#[serde(rename_all = "camelCase")]
211pub enum ClosedWorldComposesScanStateV0 {
212    ScannedClosed,
213    #[default]
214    SourceSetOpen,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct ClosedWorldModuleMetadataV0 {
219    module_instance: ModuleInstanceKeyV0,
220    interface_hash: Option<String>,
221    source_precision: Option<ClosedWorldSourcePrecisionSummaryV0>,
222    reachability_evidence: ClosedWorldModuleReachabilityEvidenceV0,
223    composes_scan_state: ClosedWorldComposesScanStateV0,
224}
225
226impl ClosedWorldModuleMetadataV0 {
227    pub fn new(module_instance: ModuleInstanceKeyV0) -> Self {
228        Self {
229            module_instance,
230            interface_hash: None,
231            source_precision: None,
232            reachability_evidence:
233                ClosedWorldModuleReachabilityEvidenceV0::ModuleReachabilityInputAbsent,
234            composes_scan_state: ClosedWorldComposesScanStateV0::SourceSetOpen,
235        }
236    }
237
238    pub fn module_instance(&self) -> &ModuleInstanceKeyV0 {
239        &self.module_instance
240    }
241
242    pub fn with_interface_hash(mut self, interface_hash: impl Into<String>) -> Self {
243        self.interface_hash = Some(interface_hash.into());
244        self
245    }
246
247    pub fn interface_hash(&self) -> Option<&str> {
248        self.interface_hash.as_deref()
249    }
250
251    pub fn with_source_precision(
252        mut self,
253        source_precision: ClosedWorldSourcePrecisionSummaryV0,
254    ) -> Self {
255        self.source_precision = Some(source_precision);
256        self
257    }
258
259    pub fn source_precision(&self) -> Option<ClosedWorldSourcePrecisionSummaryV0> {
260        self.source_precision
261    }
262
263    pub fn with_reachability_evidence(
264        mut self,
265        reachability_evidence: ClosedWorldModuleReachabilityEvidenceV0,
266    ) -> Self {
267        self.reachability_evidence = reachability_evidence;
268        self
269    }
270
271    pub fn reachability_evidence(&self) -> ClosedWorldModuleReachabilityEvidenceV0 {
272        self.reachability_evidence
273    }
274
275    pub fn with_composes_scan_state(
276        mut self,
277        composes_scan_state: ClosedWorldComposesScanStateV0,
278    ) -> Self {
279        self.composes_scan_state = composes_scan_state;
280        self
281    }
282
283    pub fn composes_scan_state(&self) -> ClosedWorldComposesScanStateV0 {
284        self.composes_scan_state
285    }
286}
287
288#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
289#[serde(rename_all = "camelCase", tag = "status")]
290pub enum ClosedWorldInterfaceHashAvailabilityV0 {
291    Known { interface_hash: String },
292    Absent,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
296#[serde(rename_all = "camelCase")]
297pub struct ClosedWorldInterfaceHashEntryV0 {
298    pub module_instance: ModuleInstanceKeyV0,
299    pub availability: ClosedWorldInterfaceHashAvailabilityV0,
300}
301
302#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
303#[serde(rename_all = "camelCase")]
304pub struct ClosedWorldInterfaceHashSetV0 {
305    entries: Vec<ClosedWorldInterfaceHashEntryV0>,
306}
307
308impl ClosedWorldInterfaceHashSetV0 {
309    pub(crate) fn new(entries: Vec<ClosedWorldInterfaceHashEntryV0>) -> Self {
310        Self { entries }
311    }
312
313    pub fn entries(&self) -> &[ClosedWorldInterfaceHashEntryV0] {
314        &self.entries
315    }
316
317    pub fn all_absent(&self) -> bool {
318        self.entries.iter().all(|entry| {
319            matches!(
320                entry.availability,
321                ClosedWorldInterfaceHashAvailabilityV0::Absent
322            )
323        })
324    }
325}
326
327#[derive(Debug, Clone, Serialize)]
328#[serde(rename_all = "camelCase")]
329pub struct ModuleQualifiedSymbolSetV0 {
330    module_instance: ModuleInstanceKeyV0,
331    reachable: bool,
332    class_names: Vec<String>,
333    keyframe_names: Vec<String>,
334    value_names: Vec<String>,
335    custom_property_names: Vec<AuthoredPropertyTextV0>,
336}
337
338impl PartialEq for ModuleQualifiedSymbolSetV0 {
339    fn eq(&self, other: &Self) -> bool {
340        self.module_instance == other.module_instance
341            && self.reachable == other.reachable
342            && self.class_names == other.class_names
343            && self.keyframe_names == other.keyframe_names
344            && self.value_names == other.value_names
345            && authored_custom_property_sequences_same(
346                &self.custom_property_names,
347                &other.custom_property_names,
348            )
349    }
350}
351
352impl Eq for ModuleQualifiedSymbolSetV0 {}
353
354impl ModuleQualifiedSymbolSetV0 {
355    pub(crate) fn new(
356        module_instance: ModuleInstanceKeyV0,
357        reachable: bool,
358        class_names: Vec<String>,
359        keyframe_names: Vec<String>,
360        value_names: Vec<String>,
361        custom_property_names: Vec<AuthoredPropertyTextV0>,
362    ) -> Self {
363        Self {
364            module_instance,
365            reachable,
366            class_names,
367            keyframe_names,
368            value_names,
369            custom_property_names,
370        }
371    }
372
373    pub fn module_instance(&self) -> &ModuleInstanceKeyV0 {
374        &self.module_instance
375    }
376
377    pub fn is_reachable(&self) -> bool {
378        self.reachable
379    }
380
381    pub fn class_names(&self) -> &[String] {
382        &self.class_names
383    }
384
385    pub fn keyframe_names(&self) -> &[String] {
386        &self.keyframe_names
387    }
388
389    pub fn value_names(&self) -> &[String] {
390        &self.value_names
391    }
392
393    pub fn custom_property_names(&self) -> &[AuthoredPropertyTextV0] {
394        &self.custom_property_names
395    }
396}
397
398#[derive(Debug, Clone, Serialize)]
399#[serde(rename_all = "camelCase")]
400pub struct ReachabilityIndexV0 {
401    module_instances: Vec<ModuleInstanceKeyV0>,
402    module_qualified_symbols: Vec<ModuleQualifiedSymbolSetV0>,
403    class_names: Vec<String>,
404    keyframe_names: Vec<String>,
405    value_names: Vec<String>,
406    custom_property_names: Vec<AuthoredPropertyTextV0>,
407}
408
409impl PartialEq for ReachabilityIndexV0 {
410    fn eq(&self, other: &Self) -> bool {
411        self.module_instances == other.module_instances
412            && self.module_qualified_symbols == other.module_qualified_symbols
413            && self.class_names == other.class_names
414            && self.keyframe_names == other.keyframe_names
415            && self.value_names == other.value_names
416            && authored_custom_property_sequences_same(
417                &self.custom_property_names,
418                &other.custom_property_names,
419            )
420    }
421}
422
423impl Eq for ReachabilityIndexV0 {}
424
425impl ReachabilityIndexV0 {
426    pub(crate) fn from_parts(
427        module_instances: Vec<ModuleInstanceKeyV0>,
428        module_qualified_symbols: Vec<ModuleQualifiedSymbolSetV0>,
429        class_names: Vec<String>,
430        keyframe_names: Vec<String>,
431        value_names: Vec<String>,
432        custom_property_names: Vec<AuthoredPropertyTextV0>,
433    ) -> Self {
434        Self {
435            module_instances,
436            module_qualified_symbols,
437            class_names,
438            keyframe_names,
439            value_names,
440            custom_property_names,
441        }
442    }
443
444    pub fn module_instances(&self) -> &[ModuleInstanceKeyV0] {
445        &self.module_instances
446    }
447
448    pub fn module_qualified_symbols(&self) -> &[ModuleQualifiedSymbolSetV0] {
449        &self.module_qualified_symbols
450    }
451
452    pub fn symbols_for_module(
453        &self,
454        module_instance: &ModuleInstanceKeyV0,
455    ) -> Option<&ModuleQualifiedSymbolSetV0> {
456        self.module_qualified_symbols
457            .binary_search_by(|entry| entry.module_instance().cmp(module_instance))
458            .ok()
459            .map(|index| &self.module_qualified_symbols[index])
460    }
461
462    pub fn class_names(&self) -> &[String] {
463        &self.class_names
464    }
465
466    pub fn keyframe_names(&self) -> &[String] {
467        &self.keyframe_names
468    }
469
470    pub fn value_names(&self) -> &[String] {
471        &self.value_names
472    }
473
474    pub fn custom_property_names(&self) -> &[AuthoredPropertyTextV0] {
475        &self.custom_property_names
476    }
477}
478
479fn authored_custom_property_sequences_same(
480    left: &[AuthoredPropertyTextV0],
481    right: &[AuthoredPropertyTextV0],
482) -> bool {
483    left.len() == right.len()
484        && left
485            .iter()
486            .zip(right)
487            .all(|(left, right)| left.to_custom_key() == right.to_custom_key())
488}
489
490#[cfg(test)]
491mod identity_tests {
492    use super::*;
493
494    fn instance() -> ModuleInstanceKeyV0 {
495        ModuleInstanceKeyV0::unconfigured(ModuleIdV0::new("/workspace/app.module.css"))
496    }
497
498    #[test]
499    fn closed_world_linked_module_identity_uses_custom_property_keys() {
500        let module = |property: &str| {
501            ClosedWorldLinkedModuleV0::new(instance())
502                .with_custom_property_name(AuthoredPropertyTextV0::new(property))
503        };
504
505        assert_eq!(module(r"--f\6f o"), module("--foo"));
506        assert_ne!(module("--foo"), module("--FOO"));
507    }
508
509    #[test]
510    fn module_qualified_symbol_set_identity_uses_custom_property_keys() {
511        let symbols = |property: &str| {
512            ModuleQualifiedSymbolSetV0::new(
513                instance(),
514                true,
515                Vec::new(),
516                Vec::new(),
517                Vec::new(),
518                vec![AuthoredPropertyTextV0::new(property)],
519            )
520        };
521
522        assert_eq!(symbols(r"--f\6f o"), symbols("--foo"));
523        assert_ne!(symbols("--foo"), symbols("--FOO"));
524    }
525
526    #[test]
527    fn reachability_index_identity_uses_custom_property_keys() {
528        let index = |property: &str| {
529            ReachabilityIndexV0::from_parts(
530                vec![instance()],
531                Vec::new(),
532                Vec::new(),
533                Vec::new(),
534                Vec::new(),
535                vec![AuthoredPropertyTextV0::new(property)],
536            )
537        };
538
539        assert_eq!(index(r"--f\6f o"), index("--foo"));
540        assert_ne!(index("--foo"), index("--FOO"));
541    }
542}
543
544#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
545#[serde(rename_all = "camelCase")]
546pub struct ClosedWorldReachabilityBitsetParityReportV0 {
547    pub schema_version: &'static str,
548    pub product: &'static str,
549    pub module_instance_count: usize,
550    pub symbol_name_count: usize,
551    pub module_qualified_symbols: Vec<ModuleQualifiedSymbolSetV0>,
552    pub reachability_equal: bool,
553    pub closure_hash_equal: bool,
554    pub btreeset_closure_hash: String,
555    pub bitset_closure_hash: String,
556}
557
558/// Closed-world bundle constructed from linked module facts.
559///
560/// External callers cannot use field-literal construction:
561///
562/// ```compile_fail
563/// use omena_parser::ClosedWorldBundleV0;
564///
565/// let _bundle = ClosedWorldBundleV0 {
566///     closure_hash: String::new(),
567/// };
568/// ```
569#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
570#[serde(rename_all = "camelCase")]
571pub struct ClosedWorldBundleV0 {
572    entrypoints: Vec<ModuleInstanceKeyV0>,
573    linked_modules: Vec<ModuleInstanceKeyV0>,
574    reachability: ReachabilityIndexV0,
575    closure_hash: String,
576    #[serde(skip_serializing_if = "ClosedWorldInterfaceHashSetV0::all_absent")]
577    interface_hashes: ClosedWorldInterfaceHashSetV0,
578    #[serde(skip_serializing_if = "Option::is_none")]
579    source_precision: Option<ClosedWorldSourcePrecisionSummaryV0>,
580    #[serde(skip_serializing_if = "Vec::is_empty")]
581    composes_edges: Vec<ClosedWorldComposesEdgeV0>,
582    #[serde(skip)]
583    composes_scan_state: ClosedWorldComposesScanStateV0,
584    #[serde(skip)]
585    composes_edge_observation_count: usize,
586    #[serde(skip)]
587    composes_origin_class_names: BTreeMap<ModuleInstanceKeyV0, Vec<String>>,
588    #[serde(skip)]
589    module_reachability_evidence:
590        BTreeMap<ModuleInstanceKeyV0, ClosedWorldModuleReachabilityEvidenceV0>,
591}
592
593pub(super) struct ClosedWorldBundleEvidenceV0 {
594    pub(super) interface_hashes: ClosedWorldInterfaceHashSetV0,
595    pub(super) source_precision: Option<ClosedWorldSourcePrecisionSummaryV0>,
596    pub(super) composes_edges: Vec<ClosedWorldComposesEdgeV0>,
597    pub(super) composes_scan_state: ClosedWorldComposesScanStateV0,
598    pub(super) composes_edge_observation_count: usize,
599    pub(super) composes_origin_class_names: BTreeMap<ModuleInstanceKeyV0, Vec<String>>,
600    pub(super) module_reachability_evidence:
601        BTreeMap<ModuleInstanceKeyV0, ClosedWorldModuleReachabilityEvidenceV0>,
602}
603
604impl ClosedWorldBundleV0 {
605    pub(super) fn seal(
606        entrypoints: Vec<ModuleInstanceKeyV0>,
607        linked_modules: Vec<ModuleInstanceKeyV0>,
608        reachability: ReachabilityIndexV0,
609        closure_hash: String,
610        evidence: ClosedWorldBundleEvidenceV0,
611    ) -> Self {
612        let ClosedWorldBundleEvidenceV0 {
613            interface_hashes,
614            source_precision,
615            composes_edges,
616            composes_scan_state,
617            composes_edge_observation_count,
618            composes_origin_class_names,
619            module_reachability_evidence,
620        } = evidence;
621        Self {
622            entrypoints,
623            linked_modules,
624            reachability,
625            closure_hash,
626            interface_hashes,
627            source_precision,
628            composes_edges,
629            composes_scan_state,
630            composes_edge_observation_count,
631            composes_origin_class_names,
632            module_reachability_evidence,
633        }
634    }
635
636    pub fn entrypoints(&self) -> &[ModuleInstanceKeyV0] {
637        &self.entrypoints
638    }
639
640    pub fn linked_modules(&self) -> &[ModuleInstanceKeyV0] {
641        &self.linked_modules
642    }
643
644    pub fn reachability(&self) -> &ReachabilityIndexV0 {
645        &self.reachability
646    }
647
648    pub fn closure_hash(&self) -> &str {
649        &self.closure_hash
650    }
651
652    pub fn module_qualified_ownership_digest(&self) -> String {
653        let mut digest = StableModuleOwnershipDigestV0::new();
654        let mut modules = self
655            .reachability
656            .module_qualified_symbols()
657            .iter()
658            .filter(|symbols| symbols.is_reachable())
659            .collect::<Vec<_>>();
660        modules.sort_by(|left, right| left.module_instance().cmp(right.module_instance()));
661
662        for symbols in modules {
663            digest.module(symbols.module_instance());
664            digest.symbol_names("class", symbols.class_names());
665            digest.symbol_names("keyframe", symbols.keyframe_names());
666            digest.symbol_names("value", symbols.value_names());
667            digest.custom_property_names(symbols.custom_property_names());
668        }
669
670        digest.finish_hex()
671    }
672
673    pub fn interface_hashes(&self) -> &ClosedWorldInterfaceHashSetV0 {
674        &self.interface_hashes
675    }
676
677    pub fn source_precision(&self) -> Option<ClosedWorldSourcePrecisionSummaryV0> {
678        self.source_precision
679    }
680
681    pub fn composes_edges(&self) -> &[ClosedWorldComposesEdgeV0] {
682        self.composes_edges.as_slice()
683    }
684
685    pub fn composes_scan_state(&self) -> ClosedWorldComposesScanStateV0 {
686        self.composes_scan_state
687    }
688
689    pub fn composes_edge_observation_count(&self) -> usize {
690        self.composes_edge_observation_count
691    }
692
693    pub fn composes_origin_symbol_is_reachable(
694        &self,
695        module_instance: &ModuleInstanceKeyV0,
696        symbol: &str,
697    ) -> Option<bool> {
698        match self
699            .module_reachability_evidence
700            .get(module_instance)
701            .copied()
702        {
703            Some(ClosedWorldModuleReachabilityEvidenceV0::Supplied) => Some(
704                self.composes_origin_class_names
705                    .get(module_instance)
706                    .is_some_and(|class_names| class_names.iter().any(|name| name == symbol)),
707            ),
708            Some(ClosedWorldModuleReachabilityEvidenceV0::ModuleReachabilityInputAbsent) | None => {
709                None
710            }
711        }
712    }
713
714    pub fn module_reachability_evidence(
715        &self,
716        module_instance: &ModuleInstanceKeyV0,
717    ) -> ClosedWorldModuleReachabilityEvidenceV0 {
718        self.module_reachability_evidence
719            .get(module_instance)
720            .copied()
721            .unwrap_or_default()
722    }
723}
724
725struct StableModuleOwnershipDigestV0(u64);
726
727impl StableModuleOwnershipDigestV0 {
728    fn new() -> Self {
729        let mut digest = Self(0xcbf2_9ce4_8422_2325);
730        digest.piece("omena-parser.module-qualified-ownership");
731        digest
732    }
733
734    fn piece(&mut self, value: &str) {
735        for byte in value.as_bytes().iter().copied().chain([0]) {
736            self.0 ^= u64::from(byte);
737            self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
738        }
739    }
740
741    fn module(&mut self, module_instance: &ModuleInstanceKeyV0) {
742        self.piece("module");
743        self.piece(module_instance.module().as_str());
744        self.piece(module_instance.configuration().as_str());
745    }
746
747    fn symbol_names(&mut self, kind: &str, names: &[String]) {
748        self.piece(kind);
749        let names = names
750            .iter()
751            .map(String::as_str)
752            .collect::<std::collections::BTreeSet<_>>();
753        for name in names {
754            self.piece(name);
755        }
756    }
757
758    fn custom_property_names(&mut self, names: &[AuthoredPropertyTextV0]) {
759        self.piece("custom-property");
760        let names = names
761            .iter()
762            .map(AuthoredPropertyTextV0::to_custom_key)
763            .collect::<std::collections::BTreeSet<_>>();
764        for name in names {
765            self.piece(name.as_str());
766        }
767    }
768
769    fn finish_hex(self) -> String {
770        format!("{:016x}", self.0)
771    }
772}
773
774#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
775#[serde(rename_all = "camelCase")]
776pub struct OpenWorldSnapshotV0 {
777    reason: String,
778}
779
780impl OpenWorldSnapshotV0 {
781    pub fn new(reason: impl Into<String>) -> Self {
782        Self {
783            reason: reason.into(),
784        }
785    }
786
787    pub fn reason(&self) -> &str {
788        &self.reason
789    }
790}
791
792#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
793#[serde(rename_all = "camelCase")]
794pub enum ClosedWorldBundleBuildErrorV0 {
795    EmptyEntrypoints,
796    MissingEntrypoint {
797        module: ModuleInstanceKeyV0,
798    },
799    MissingDependency {
800        module: ModuleInstanceKeyV0,
801        dependency: ModuleInstanceKeyV0,
802    },
803}