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