1use 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#[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}