1use crate::view_state::{ViewEpoch, ViewState, ViewVersion};
4
5#[derive(Clone, Debug)]
25pub struct ViewBoundContext<T> {
26 pub data: T,
28 pub bound_at: ViewVersion,
30 pub bound_epoch: ViewEpoch,
32}
33
34impl<T> ViewBoundContext<T> {
35 #[must_use]
37 pub fn bind(data: T, view: &ViewState) -> Self {
38 Self {
39 data,
40 bound_at: view.version,
41 bound_epoch: view.epoch,
42 }
43 }
44
45 #[must_use]
47 pub fn validity(&self, current: &ViewState) -> BoundContextValidity {
48 if current.version == self.bound_at {
49 BoundContextValidity::Current
50 } else if current.epoch == self.bound_epoch {
51 BoundContextValidity::StaleWithinEpoch {
52 versions_behind: current.version.versions_since(self.bound_at),
53 }
54 } else {
55 BoundContextValidity::InvalidAcrossEpoch {
56 epochs_behind: current
57 .epoch
58 .as_u64()
59 .saturating_sub(self.bound_epoch.as_u64()),
60 }
61 }
62 }
63
64 pub fn rebind(&mut self, view: &ViewState) {
68 self.bound_at = view.version;
69 self.bound_epoch = view.epoch;
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub enum BoundContextValidity {
76 Current,
78
79 StaleWithinEpoch {
83 versions_behind: u64,
85 },
86
87 InvalidAcrossEpoch {
90 epochs_behind: u64,
92 },
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn current_when_same_version() {
101 let view = ViewState::fixed_initial();
102 let bound = ViewBoundContext::bind(42u32, &view);
103 assert_eq!(bound.validity(&view), BoundContextValidity::Current);
104 }
105
106 #[test]
107 fn stale_within_epoch() {
108 let view = ViewState::fixed_initial();
109 let bound = ViewBoundContext::bind(42u32, &view);
110 let mut updated = view.clone();
111 updated.version = ViewVersion::new(5);
112 assert_eq!(
113 bound.validity(&updated),
114 BoundContextValidity::StaleWithinEpoch { versions_behind: 5 }
115 );
116 }
117
118 #[test]
119 fn invalid_across_epoch() {
120 let view = ViewState::fixed_initial();
121 let bound = ViewBoundContext::bind(42u32, &view);
122 let mut updated = view.clone();
123 updated.epoch = ViewEpoch::new(3);
124 updated.version = ViewVersion::new(10);
125 assert_eq!(
126 bound.validity(&updated),
127 BoundContextValidity::InvalidAcrossEpoch { epochs_behind: 3 }
128 );
129 }
130
131 #[test]
132 fn rebind_resets_validity() {
133 let view = ViewState::fixed_initial();
134 let mut bound = ViewBoundContext::bind(42u32, &view);
135 let mut updated = view.clone();
136 updated.version = ViewVersion::new(5);
137 bound.rebind(&updated);
138 assert_eq!(bound.validity(&updated), BoundContextValidity::Current);
139 }
140}