Skip to main content

reifydb_value/config/
window.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use super::Config;
5use crate::value::duration::Duration;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct WindowSealing {
9	pub lateness: Option<Duration>,
10	pub immutable: Option<Duration>,
11}
12
13impl Config {
14	pub fn sealing(&self) -> Option<WindowSealing> {
15		self.resolve_sealing().ok()
16	}
17
18	pub fn validated_sealing(&self) -> WindowSealing {
19		match self.resolve_sealing() {
20			Ok(sealing) => sealing,
21			Err(violation) => panic!("{}: {}", self.name, violation),
22		}
23	}
24
25	fn resolve_immutable(&self) -> Option<Duration> {
26		match self.bool("immutable") {
27			Some(true) => Some(Duration::zero()),
28			Some(false) => None,
29			None => self.duration("immutable"),
30		}
31	}
32
33	fn resolve_sealing(&self) -> Result<WindowSealing, String> {
34		let sealing = WindowSealing {
35			lateness: self.duration("lateness"),
36			immutable: self.resolve_immutable(),
37		};
38		if let (Some(lateness), Some(immutable)) = (sealing.lateness, sealing.immutable)
39			&& immutable >= lateness
40		{
41			return Err(format!("immutable {immutable} must be strictly less than lateness {lateness}"));
42		}
43		Ok(sealing)
44	}
45}
46
47#[cfg(test)]
48mod tests {
49	use super::{super::testutil::config, WindowSealing};
50	use crate::value::{Value, duration::Duration};
51
52	fn secs(n: i64) -> Duration {
53		Duration::from_seconds(n).unwrap()
54	}
55
56	#[test]
57	fn declared_immutable_below_lateness_is_kept_verbatim() {
58		let cfg =
59			config(vec![("lateness", Value::Duration(secs(20))), ("immutable", Value::Duration(secs(15)))]);
60		assert_eq!(
61			cfg.sealing(),
62			Some(WindowSealing {
63				lateness: Some(secs(20)),
64				immutable: Some(secs(15)),
65			})
66		);
67	}
68
69	#[test]
70	fn lateness_without_immutable_resolves_with_no_immutable() {
71		// Substituting the lateness would arm the sealing slots on a window that never asked to seal.
72		let cfg = config(vec![("lateness", Value::Duration(secs(20)))]);
73		assert_eq!(
74			cfg.sealing(),
75			Some(WindowSealing {
76				lateness: Some(secs(20)),
77				immutable: None,
78			})
79		);
80	}
81
82	#[test]
83	fn declared_immutable_equal_to_lateness_is_rejected() {
84		// The bound is strict; an immutable equal to the lateness would never seal before the window closes.
85		let cfg =
86			config(vec![("lateness", Value::Duration(secs(20))), ("immutable", Value::Duration(secs(20)))]);
87		assert_eq!(cfg.sealing(), None);
88	}
89
90	#[test]
91	fn declared_immutable_above_lateness_is_rejected() {
92		let cfg =
93			config(vec![("lateness", Value::Duration(secs(20))), ("immutable", Value::Duration(secs(30)))]);
94		assert_eq!(cfg.sealing(), None);
95	}
96
97	#[test]
98	fn immutable_without_lateness_is_accepted() {
99		// The ordering bound needs both knobs, so an immutable alone must reach the operator untouched.
100		let cfg = config(vec![("immutable", Value::Duration(secs(15)))]);
101		assert_eq!(
102			cfg.sealing(),
103			Some(WindowSealing {
104				lateness: None,
105				immutable: Some(secs(15)),
106			})
107		);
108	}
109
110	#[test]
111	fn neither_knob_declared_leaves_both_absent() {
112		// An undeclared knob must stay absent, never resolve to zero, which is itself a legal declared value.
113		let cfg = config(vec![("duration", Value::Duration(secs(60)))]);
114		assert_eq!(
115			cfg.sealing(),
116			Some(WindowSealing {
117				lateness: None,
118				immutable: None,
119			})
120		);
121	}
122
123	#[test]
124	fn validated_returns_both_when_declared() {
125		let cfg =
126			config(vec![("lateness", Value::Duration(secs(20))), ("immutable", Value::Duration(secs(15)))]);
127		assert_eq!(
128			cfg.validated_sealing(),
129			WindowSealing {
130				lateness: Some(secs(20)),
131				immutable: Some(secs(15)),
132			}
133		);
134	}
135
136	#[test]
137	fn validated_returns_no_immutable_when_only_the_lateness_is_declared() {
138		// A window under the immutable floor declares a lateness alone and must still resolve.
139		let cfg = config(vec![("lateness", Value::Duration(secs(20)))]);
140		assert_eq!(
141			cfg.validated_sealing(),
142			WindowSealing {
143				lateness: Some(secs(20)),
144				immutable: None,
145			}
146		);
147	}
148
149	#[test]
150	#[should_panic(expected = "must be strictly less than lateness")]
151	fn validated_names_the_ordering_violation() {
152		let cfg =
153			config(vec![("lateness", Value::Duration(secs(20))), ("immutable", Value::Duration(secs(20)))]);
154		cfg.validated_sealing();
155	}
156
157	#[test]
158	fn validated_accepts_an_immutable_without_a_lateness() {
159		// Validation covers the ordering rule only; neither knob is required, so this must not panic.
160		let cfg = config(vec![("immutable", Value::Duration(secs(15)))]);
161		assert_eq!(
162			cfg.validated_sealing(),
163			WindowSealing {
164				lateness: None,
165				immutable: Some(secs(15)),
166			}
167		);
168	}
169
170	#[test]
171	fn validated_accepts_a_window_that_declares_no_knob_at_all() {
172		let cfg = config(vec![("duration", Value::Duration(secs(60)))]);
173		assert_eq!(
174			cfg.validated_sealing(),
175			WindowSealing {
176				lateness: None,
177				immutable: None,
178			}
179		);
180	}
181
182	#[test]
183	fn boolean_true_resolves_to_a_zero_duration() {
184		let cfg = config(vec![("lateness", Value::Duration(secs(20))), ("immutable", Value::Boolean(true))]);
185		assert_eq!(
186			cfg.sealing(),
187			Some(WindowSealing {
188				lateness: Some(secs(20)),
189				immutable: Some(Duration::zero()),
190			})
191		);
192	}
193
194	#[test]
195	fn boolean_false_resolves_as_if_absent() {
196		let cfg = config(vec![("lateness", Value::Duration(secs(20))), ("immutable", Value::Boolean(false))]);
197		assert_eq!(
198			cfg.sealing(),
199			Some(WindowSealing {
200				lateness: Some(secs(20)),
201				immutable: None,
202			})
203		);
204	}
205}