1use crate::util::ArenaIndex;
7use core::fmt;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::ops::Add;
10use std::sync::atomic::{AtomicU32, Ordering};
11use std::time::Duration;
12
13static EPHEMERAL_REGION_COUNTER: AtomicU32 = AtomicU32::new(1);
22static EPHEMERAL_TASK_COUNTER: AtomicU32 = AtomicU32::new(1);
23
24#[inline]
31#[must_use]
32pub(crate) fn next_bootstrap_region_id() -> RegionId {
33 let index = EPHEMERAL_REGION_COUNTER.fetch_add(1, Ordering::Relaxed);
34 RegionId(ArenaIndex::new(index, 1))
35}
36
37#[inline]
40#[must_use]
41pub(crate) fn next_bootstrap_task_id() -> TaskId {
42 let index = EPHEMERAL_TASK_COUNTER.fetch_add(1, Ordering::Relaxed);
43 TaskId(ArenaIndex::new(index, 1))
44}
45
46#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
50pub struct RegionId(pub(crate) ArenaIndex);
51
52impl RegionId {
53 #[inline]
55 #[must_use]
56 #[cfg_attr(feature = "test-internals", visibility::make(pub))]
57 pub(crate) const fn from_arena(index: ArenaIndex) -> Self {
58 Self(index)
59 }
60
61 #[inline]
63 #[must_use]
64 pub fn as_u64(&self) -> u64 {
65 ((self.0.generation() as u64) << 32) | (self.0.index() as u64)
66 }
67
68 #[inline]
70 #[must_use]
71 #[allow(dead_code)]
72 #[cfg(not(feature = "test-internals"))]
73 pub(crate) const fn arena_index(self) -> ArenaIndex {
74 self.0
75 }
76
77 #[inline]
79 #[must_use]
80 #[allow(dead_code)]
81 #[cfg(feature = "test-internals")]
82 pub const fn arena_index(self) -> ArenaIndex {
83 self.0
84 }
85
86 #[doc(hidden)]
104 #[cfg(any(test, feature = "test-internals"))]
105 #[inline]
106 #[must_use]
107 pub const fn new_for_test(index: u32, generation: u32) -> Self {
108 Self(ArenaIndex::new(index, generation))
109 }
110
111 #[doc(hidden)]
116 #[inline]
117 #[must_use]
118 pub const fn testing_default() -> Self {
119 Self(ArenaIndex::new(0, 0))
120 }
121
122 #[doc(hidden)]
136 #[cfg(any(test, feature = "test-internals"))]
137 #[inline]
138 #[must_use]
139 pub fn new_ephemeral() -> Self {
140 next_bootstrap_region_id()
141 }
142}
143
144impl fmt::Debug for RegionId {
145 #[inline]
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "RegionId({}:{})", self.0.index(), self.0.generation())
148 }
149}
150
151impl fmt::Display for RegionId {
152 #[inline]
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(f, "R{}", self.0.index())
155 }
156}
157
158const KIND_REGION_ID: &str = "RegionId";
177const KIND_TASK_ID: &str = "TaskId";
178const KIND_OBLIGATION_ID: &str = "ObligationId";
179#[derive(Debug, Clone, Serialize, Deserialize)]
184struct SerdeIdEnvelope {
185 kind: String,
186 index: u32,
187 generation: u32,
188}
189
190impl SerdeIdEnvelope {
191 #[inline]
192 fn from_arena(arena: ArenaIndex, kind: &'static str) -> Self {
193 Self {
194 kind: kind.to_string(),
195 index: arena.index(),
196 generation: arena.generation(),
197 }
198 }
199
200 #[inline]
201 fn to_arena(&self) -> ArenaIndex {
202 ArenaIndex::new(self.index, self.generation)
203 }
204
205 #[inline]
206 fn check_kind<E>(&self, expected: &'static str) -> Result<(), E>
207 where
208 E: serde::de::Error,
209 {
210 if self.kind == expected {
211 Ok(())
212 } else {
213 Err(E::custom(format!(
214 "br-asupersync-o2oa4l: ID kind mismatch — expected {expected:?}, got {:?}",
215 self.kind
216 )))
217 }
218 }
219}
220
221impl Serialize for RegionId {
222 #[inline]
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: Serializer,
226 {
227 SerdeIdEnvelope::from_arena(self.0, KIND_REGION_ID).serialize(serializer)
228 }
229}
230
231impl<'de> Deserialize<'de> for RegionId {
232 #[inline]
233 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
234 where
235 D: Deserializer<'de>,
236 {
237 let env = SerdeIdEnvelope::deserialize(deserializer)?;
238 env.check_kind::<D::Error>(KIND_REGION_ID)?;
239 Ok(Self(env.to_arena()))
240 }
241}
242
243#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
247pub struct TaskId(pub(crate) ArenaIndex);
248
249impl TaskId {
250 #[inline]
252 #[must_use]
253 #[allow(dead_code)]
254 #[cfg_attr(feature = "test-internals", visibility::make(pub))]
255 pub(crate) const fn from_arena(index: ArenaIndex) -> Self {
256 Self(index)
257 }
258
259 #[inline]
261 #[must_use]
262 pub fn as_u64(&self) -> u64 {
263 ((self.0.generation() as u64) << 32) | (self.0.index() as u64)
264 }
265
266 #[inline]
268 #[must_use]
269 #[allow(dead_code)]
270 #[cfg(not(feature = "test-internals"))]
271 pub(crate) const fn arena_index(self) -> ArenaIndex {
272 self.0
273 }
274
275 #[inline]
277 #[must_use]
278 #[allow(dead_code)]
279 #[cfg(feature = "test-internals")]
280 pub const fn arena_index(self) -> ArenaIndex {
281 self.0
282 }
283
284 #[doc(hidden)]
292 #[cfg(any(test, feature = "test-internals"))]
293 #[inline]
294 #[must_use]
295 pub const fn new_for_test(index: u32, generation: u32) -> Self {
296 Self(ArenaIndex::new(index, generation))
297 }
298
299 #[doc(hidden)]
304 #[inline]
305 #[must_use]
306 pub const fn testing_default() -> Self {
307 Self(ArenaIndex::new(0, 0))
308 }
309
310 #[doc(hidden)]
319 #[cfg(any(test, feature = "test-internals"))]
320 #[inline]
321 #[must_use]
322 pub fn new_ephemeral() -> Self {
323 next_bootstrap_task_id()
324 }
325}
326
327impl fmt::Debug for TaskId {
328 #[inline]
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 write!(f, "TaskId({}:{})", self.0.index(), self.0.generation())
331 }
332}
333
334impl fmt::Display for TaskId {
335 #[inline]
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 write!(f, "T{}", self.0.index())
338 }
339}
340
341impl Serialize for TaskId {
342 #[inline]
343 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
344 where
345 S: Serializer,
346 {
347 SerdeIdEnvelope::from_arena(self.0, KIND_TASK_ID).serialize(serializer)
348 }
349}
350
351impl<'de> Deserialize<'de> for TaskId {
352 #[inline]
353 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
354 where
355 D: Deserializer<'de>,
356 {
357 let env = SerdeIdEnvelope::deserialize(deserializer)?;
358 env.check_kind::<D::Error>(KIND_TASK_ID)?;
359 Ok(Self(env.to_arena()))
360 }
361}
362
363#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
368pub struct ObligationId(pub(crate) ArenaIndex);
369
370impl ObligationId {
371 #[inline]
373 #[must_use]
374 #[allow(dead_code)]
375 pub(crate) const fn from_arena(index: ArenaIndex) -> Self {
376 Self(index)
377 }
378
379 #[inline]
383 #[must_use]
384 pub fn as_u64(&self) -> u64 {
385 ((self.0.generation() as u64) << 32) | (self.0.index() as u64)
386 }
387
388 #[inline]
390 #[must_use]
391 #[allow(dead_code)]
392 #[cfg(not(feature = "test-internals"))]
393 pub(crate) const fn arena_index(self) -> ArenaIndex {
394 self.0
395 }
396
397 #[inline]
399 #[must_use]
400 #[allow(dead_code)]
401 #[cfg(feature = "test-internals")]
402 pub const fn arena_index(self) -> ArenaIndex {
403 self.0
404 }
405
406 #[doc(hidden)]
414 #[cfg(any(test, feature = "test-internals"))]
415 #[inline]
416 #[must_use]
417 pub const fn new_for_test(index: u32, generation: u32) -> Self {
418 Self(ArenaIndex::new(index, generation))
419 }
420}
421
422impl fmt::Debug for ObligationId {
423 #[inline]
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 write!(
426 f,
427 "ObligationId({}:{})",
428 self.0.index(),
429 self.0.generation()
430 )
431 }
432}
433
434impl fmt::Display for ObligationId {
435 #[inline]
436 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437 write!(f, "O{}", self.0.index())
438 }
439}
440
441impl Serialize for ObligationId {
442 #[inline]
443 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
444 where
445 S: Serializer,
446 {
447 SerdeIdEnvelope::from_arena(self.0, KIND_OBLIGATION_ID).serialize(serializer)
448 }
449}
450
451impl<'de> Deserialize<'de> for ObligationId {
452 #[inline]
453 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
454 where
455 D: Deserializer<'de>,
456 {
457 let env = SerdeIdEnvelope::deserialize(deserializer)?;
458 env.check_kind::<D::Error>(KIND_OBLIGATION_ID)?;
459 Ok(Self(env.to_arena()))
460 }
461}
462
463#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
468pub struct Time(u64);
469
470impl Time {
471 pub const ZERO: Self = Self(0);
473
474 pub const MAX: Self = Self(u64::MAX);
476
477 #[inline]
479 #[must_use]
480 pub const fn from_nanos(nanos: u64) -> Self {
481 Self(nanos)
482 }
483
484 #[inline]
486 #[must_use]
487 pub const fn from_millis(millis: u64) -> Self {
488 Self(millis.saturating_mul(1_000_000))
489 }
490
491 #[inline]
493 #[must_use]
494 pub const fn from_secs(secs: u64) -> Self {
495 Self(secs.saturating_mul(1_000_000_000))
496 }
497
498 #[inline]
500 #[must_use]
501 pub const fn as_nanos(self) -> u64 {
502 self.0
503 }
504
505 #[inline]
507 #[must_use]
508 pub const fn as_millis(self) -> u64 {
509 self.0 / 1_000_000
510 }
511
512 #[inline]
514 #[must_use]
515 pub const fn as_secs(self) -> u64 {
516 self.0 / 1_000_000_000
517 }
518
519 #[inline]
521 #[must_use]
522 pub const fn saturating_add_nanos(self, nanos: u64) -> Self {
523 Self(self.0.saturating_add(nanos))
524 }
525
526 #[inline]
528 #[must_use]
529 pub const fn saturating_sub_nanos(self, nanos: u64) -> Self {
530 Self(self.0.saturating_sub(nanos))
531 }
532
533 #[inline]
538 #[must_use]
539 pub const fn duration_since(self, earlier: Self) -> u64 {
540 self.0.saturating_sub(earlier.0)
541 }
542}
543
544impl Add<Duration> for Time {
545 type Output = Self;
546
547 #[inline]
548 fn add(self, rhs: Duration) -> Self::Output {
549 let nanos: u64 = rhs.as_nanos().min(u128::from(u64::MAX)) as u64;
550 self.saturating_add_nanos(nanos)
551 }
552}
553
554impl fmt::Debug for Time {
555 #[inline]
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 write!(f, "Time({}ns)", self.0)
558 }
559}
560
561impl fmt::Display for Time {
562 #[inline]
563 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
564 if self.0 >= 1_000_000_000 {
565 write!(
566 f,
567 "{}.{:03}s",
568 self.0 / 1_000_000_000,
569 (self.0 / 1_000_000) % 1000
570 )
571 } else if self.0 >= 1_000_000 {
572 write!(f, "{}ms", self.0 / 1_000_000)
573 } else if self.0 >= 1_000 {
574 write!(f, "{}us", self.0 / 1_000)
575 } else {
576 write!(f, "{}ns", self.0)
577 }
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 #![allow(
584 clippy::pedantic,
585 clippy::nursery,
586 clippy::expect_fun_call,
587 clippy::map_unwrap_or,
588 clippy::cast_possible_wrap,
589 clippy::future_not_send
590 )]
591 use super::*;
592
593 #[test]
594 fn time_conversions() {
595 assert_eq!(Time::from_secs(1).as_nanos(), 1_000_000_000);
596 assert_eq!(Time::from_millis(1).as_nanos(), 1_000_000);
597 assert_eq!(Time::from_nanos(1).as_nanos(), 1);
598
599 assert_eq!(Time::from_nanos(1_500_000_000).as_secs(), 1);
600 assert_eq!(Time::from_nanos(1_500_000_000).as_millis(), 1500);
601 }
602
603 #[test]
604 fn time_arithmetic() {
605 let t1 = Time::from_secs(1);
606 let t2 = t1.saturating_add_nanos(500_000_000);
607 assert_eq!(t2.as_millis(), 1500);
608
609 let t3 = t2.saturating_sub_nanos(2_000_000_000);
610 assert_eq!(t3, Time::ZERO);
611 }
612
613 #[test]
614 fn time_ordering() {
615 assert!(Time::from_secs(1) < Time::from_secs(2));
616 assert!(Time::from_millis(1000) == Time::from_secs(1));
617 }
618
619 #[test]
622 fn region_id_debug_format() {
623 let id = RegionId::new_for_test(5, 3);
624 let dbg = format!("{id:?}");
625 assert!(dbg.contains("RegionId"), "{dbg}");
626 assert!(dbg.contains('5'), "{dbg}");
627 assert!(dbg.contains('3'), "{dbg}");
628 }
629
630 #[test]
631 fn region_id_display_format() {
632 let id = RegionId::new_for_test(42, 0);
633 assert_eq!(format!("{id}"), "R42");
634 }
635
636 #[test]
637 fn region_id_equality_and_hash() {
638 use crate::util::DetHasher;
639 use std::hash::{Hash, Hasher};
640
641 let a = RegionId::new_for_test(1, 2);
642 let b = RegionId::new_for_test(1, 2);
643 let c = RegionId::new_for_test(1, 3);
644
645 assert_eq!(a, b);
646 assert_ne!(a, c);
647
648 let mut ha = DetHasher::default();
649 let mut hb = DetHasher::default();
650 a.hash(&mut ha);
651 b.hash(&mut hb);
652 assert_eq!(ha.finish(), hb.finish());
653 }
654
655 #[test]
656 fn region_id_ordering() {
657 let a = RegionId::new_for_test(1, 0);
658 let b = RegionId::new_for_test(2, 0);
659 assert!(a < b);
660 assert!(a <= b);
661 assert!(b > a);
662 }
663
664 #[test]
665 fn region_id_copy_clone() {
666 let id = RegionId::new_for_test(1, 0);
667 let copied = id;
668 let cloned = id;
669 assert_eq!(id, copied);
670 assert_eq!(id, cloned);
671 }
672
673 #[test]
674 fn region_id_testing_default() {
675 let id = RegionId::testing_default();
676 assert_eq!(format!("{id}"), "R0");
677 }
678
679 #[test]
680 fn region_id_ephemeral_unique() {
681 let a = RegionId::new_ephemeral();
682 let b = RegionId::new_ephemeral();
683 assert_ne!(a, b);
684 }
685
686 #[test]
687 fn region_id_serde_roundtrip() {
688 let id = RegionId::new_for_test(99, 7);
689 let json = serde_json::to_string(&id).expect("serialize");
690 let deserialized: RegionId = serde_json::from_str(&json).expect("deserialize");
691 assert_eq!(id, deserialized);
692 }
693
694 #[test]
697 fn task_id_debug_format() {
698 let id = TaskId::new_for_test(10, 2);
699 let dbg = format!("{id:?}");
700 assert!(dbg.contains("TaskId"), "{dbg}");
701 assert!(dbg.contains("10"), "{dbg}");
702 assert!(dbg.contains('2'), "{dbg}");
703 }
704
705 #[test]
706 fn task_id_display_format() {
707 let id = TaskId::new_for_test(7, 0);
708 assert_eq!(format!("{id}"), "T7");
709 }
710
711 #[test]
712 fn task_id_equality_and_hash() {
713 use crate::util::DetHasher;
714 use std::hash::{Hash, Hasher};
715
716 let a = TaskId::new_for_test(3, 1);
717 let b = TaskId::new_for_test(3, 1);
718 let c = TaskId::new_for_test(3, 2);
719
720 assert_eq!(a, b);
721 assert_ne!(a, c);
722
723 let mut ha = DetHasher::default();
724 let mut hb = DetHasher::default();
725 a.hash(&mut ha);
726 b.hash(&mut hb);
727 assert_eq!(ha.finish(), hb.finish());
728 }
729
730 #[test]
731 fn task_id_ordering() {
732 let a = TaskId::new_for_test(1, 0);
733 let b = TaskId::new_for_test(2, 0);
734 assert!(a < b);
735 }
736
737 #[test]
738 fn task_id_copy_clone() {
739 let id = TaskId::new_for_test(5, 1);
740 let copied = id;
741 let cloned = id;
742 assert_eq!(id, copied);
743 assert_eq!(id, cloned);
744 }
745
746 #[test]
747 fn task_id_testing_default() {
748 let id = TaskId::testing_default();
749 assert_eq!(format!("{id}"), "T0");
750 }
751
752 #[test]
753 fn task_id_ephemeral_unique() {
754 let a = TaskId::new_ephemeral();
755 let b = TaskId::new_ephemeral();
756 assert_ne!(a, b);
757 }
758
759 #[test]
760 fn task_id_serde_roundtrip() {
761 let id = TaskId::new_for_test(42, 5);
762 let json = serde_json::to_string(&id).expect("serialize");
763 let deserialized: TaskId = serde_json::from_str(&json).expect("deserialize");
764 assert_eq!(id, deserialized);
765 }
766
767 #[test]
770 fn obligation_id_debug_format() {
771 let id = ObligationId::new_for_test(8, 1);
772 let dbg = format!("{id:?}");
773 assert!(dbg.contains("ObligationId"), "{dbg}");
774 assert!(dbg.contains('8'), "{dbg}");
775 }
776
777 #[test]
778 fn obligation_id_display_format() {
779 let id = ObligationId::new_for_test(3, 0);
780 assert_eq!(format!("{id}"), "O3");
781 }
782
783 #[test]
784 fn obligation_id_equality_and_hash() {
785 use crate::util::DetHasher;
786 use std::hash::{Hash, Hasher};
787
788 let a = ObligationId::new_for_test(1, 1);
789 let b = ObligationId::new_for_test(1, 1);
790 let c = ObligationId::new_for_test(2, 1);
791
792 assert_eq!(a, b);
793 assert_ne!(a, c);
794
795 let mut ha = DetHasher::default();
796 let mut hb = DetHasher::default();
797 a.hash(&mut ha);
798 b.hash(&mut hb);
799 assert_eq!(ha.finish(), hb.finish());
800 }
801
802 #[test]
803 fn obligation_id_ordering() {
804 let a = ObligationId::new_for_test(1, 0);
805 let b = ObligationId::new_for_test(2, 0);
806 assert!(a < b);
807 }
808
809 #[test]
810 fn obligation_id_copy_clone() {
811 let id = ObligationId::new_for_test(1, 0);
812 let copied = id;
813 let cloned = id;
814 assert_eq!(id, copied);
815 assert_eq!(id, cloned);
816 }
817
818 #[test]
819 fn obligation_id_serde_roundtrip() {
820 let id = ObligationId::new_for_test(77, 3);
821 let json = serde_json::to_string(&id).expect("serialize");
822 let deserialized: ObligationId = serde_json::from_str(&json).expect("deserialize");
823 assert_eq!(id, deserialized);
824 }
825
826 #[test]
829 fn time_display_seconds() {
830 let t = Time::from_secs(2);
831 let disp = format!("{t}");
832 assert_eq!(disp, "2.000s");
833 }
834
835 #[test]
836 fn time_display_seconds_with_millis() {
837 let t = Time::from_nanos(1_234_000_000);
838 let disp = format!("{t}");
839 assert_eq!(disp, "1.234s");
840 }
841
842 #[test]
843 fn time_display_milliseconds() {
844 let t = Time::from_millis(500);
845 let disp = format!("{t}");
846 assert_eq!(disp, "500ms");
847 }
848
849 #[test]
850 fn time_display_microseconds() {
851 let t = Time::from_nanos(5_000);
852 let disp = format!("{t}");
853 assert_eq!(disp, "5us");
854 }
855
856 #[test]
857 fn time_display_nanoseconds() {
858 let t = Time::from_nanos(42);
859 let disp = format!("{t}");
860 assert_eq!(disp, "42ns");
861 }
862
863 #[test]
864 fn time_display_zero() {
865 assert_eq!(format!("{}", Time::ZERO), "0ns");
866 }
867
868 #[test]
871 fn time_debug_format() {
872 let t = Time::from_nanos(100);
873 let dbg = format!("{t:?}");
874 assert_eq!(dbg, "Time(100ns)");
875 }
876
877 #[test]
878 fn time_default_is_zero() {
879 assert_eq!(Time::default(), Time::ZERO);
880 }
881
882 #[test]
883 fn time_max_constant() {
884 assert_eq!(Time::MAX.as_nanos(), u64::MAX);
885 }
886
887 #[test]
888 fn time_saturating_add_overflow() {
889 let t = Time::MAX;
890 let result = t.saturating_add_nanos(1);
891 assert_eq!(result, Time::MAX);
892 }
893
894 #[test]
895 fn time_saturating_sub_underflow() {
896 let t = Time::ZERO;
897 let result = t.saturating_sub_nanos(100);
898 assert_eq!(result, Time::ZERO);
899 }
900
901 #[test]
902 fn time_duration_since() {
903 let t1 = Time::from_secs(5);
904 let t2 = Time::from_secs(3);
905 assert_eq!(t1.duration_since(t2), 2_000_000_000);
906 assert_eq!(t2.duration_since(t1), 0); }
908
909 #[test]
910 fn time_add_duration() {
911 let t = Time::from_secs(1);
912 let result = t + Duration::from_millis(500);
913 assert_eq!(result.as_millis(), 1500);
914 }
915
916 #[test]
917 fn time_from_millis_saturation() {
918 let t = Time::from_millis(u64::MAX);
919 assert_eq!(t, Time::MAX);
921 }
922
923 #[test]
924 fn time_from_secs_saturation() {
925 let t = Time::from_secs(u64::MAX);
926 assert_eq!(t, Time::MAX);
927 }
928
929 #[test]
930 fn time_serde_roundtrip() {
931 let t = Time::from_nanos(12345);
932 let json = serde_json::to_string(&t).expect("serialize");
933 let deserialized: Time = serde_json::from_str(&json).expect("deserialize");
934 assert_eq!(t, deserialized);
935 }
936
937 #[test]
938 fn time_hash_consistency() {
939 use crate::util::DetHasher;
940 use std::hash::{Hash, Hasher};
941
942 let a = Time::from_secs(1);
943 let b = Time::from_millis(1000);
944 assert_eq!(a, b);
945
946 let mut ha = DetHasher::default();
947 let mut hb = DetHasher::default();
948 a.hash(&mut ha);
949 b.hash(&mut hb);
950 assert_eq!(ha.finish(), hb.finish());
951 }
952
953 #[test]
956 fn bootstrap_helpers_mint_unique_ids() {
957 let r1 = next_bootstrap_region_id();
958 let r2 = next_bootstrap_region_id();
959 assert_ne!(r1, r2);
960 assert_eq!(r1.arena_index().generation(), 1);
961 assert_eq!(r2.arena_index().generation(), 1);
962
963 let t1 = next_bootstrap_task_id();
964 let t2 = next_bootstrap_task_id();
965 assert_ne!(t1, t2);
966 assert_eq!(t1.arena_index().generation(), 1);
967 assert_eq!(t2.arena_index().generation(), 1);
968 }
969
970 #[test]
976 fn serde_rejects_cross_type_id_confusion() {
977 let region = RegionId::from_arena(ArenaIndex::new(7, 3));
978 let json = serde_json::to_string(®ion).expect("serialise RegionId");
979 assert!(json.contains("\"kind\""));
981 assert!(json.contains("RegionId"));
982
983 let region_back: RegionId = serde_json::from_str(&json).expect("RegionId round-trip");
985 assert_eq!(region_back, region);
986
987 let task_err = serde_json::from_str::<TaskId>(&json);
989 assert!(task_err.is_err(), "TaskId must reject RegionId payload");
990 let obl_err = serde_json::from_str::<ObligationId>(&json);
991 assert!(
992 obl_err.is_err(),
993 "ObligationId must reject RegionId payload"
994 );
995 }
996
997 #[test]
1000 fn serde_rejects_task_obligation_confusion() {
1001 let task = TaskId::from_arena(ArenaIndex::new(11, 2));
1002 let task_json = serde_json::to_string(&task).expect("serialise TaskId");
1003 assert!(serde_json::from_str::<RegionId>(&task_json).is_err());
1004 assert!(serde_json::from_str::<ObligationId>(&task_json).is_err());
1005 let task_back: TaskId = serde_json::from_str(&task_json).expect("TaskId round-trip");
1006 assert_eq!(task_back, task);
1007
1008 let obl = ObligationId::from_arena(ArenaIndex::new(11, 2));
1009 let obl_json = serde_json::to_string(&obl).expect("serialise ObligationId");
1010 assert!(serde_json::from_str::<RegionId>(&obl_json).is_err());
1011 assert!(serde_json::from_str::<TaskId>(&obl_json).is_err());
1012 }
1013}