1use std::fmt;
7
8pub use origin_macros::BoundaryKind;
10pub use origin_macros::boundary_check;
11
12pub trait BoundaryKind: fmt::Debug + Send + Sync + 'static {}
14
15#[derive(Debug)]
17#[must_use = "this Value may be a Boundary that must be handled"]
18pub enum Value<T, B: BoundaryKind = GenericBoundary> {
19 Interior(T),
21 Boundary {
23 reason: B,
24 last: T,
25 },
26}
27
28impl<T, B: BoundaryKind> Value<T, B> {
29 pub fn interior(value: T) -> Self {
31 Value::Interior(value)
32 }
33
34 pub fn boundary(reason: B, last: T) -> Self {
36 Value::Boundary { reason, last }
37 }
38
39 pub fn is_interior(&self) -> bool {
40 matches!(self, Value::Interior(_))
41 }
42
43 pub fn is_boundary(&self) -> bool {
44 matches!(self, Value::Boundary { .. })
45 }
46
47 pub fn or(self, fallback: T) -> T {
49 match self {
50 Value::Interior(v) => v,
51 Value::Boundary { .. } => fallback,
52 }
53 }
54
55 pub fn unwrap(self) -> T {
57 match self {
58 Value::Interior(v) => v,
59 Value::Boundary { reason, .. } => {
60 panic!("called unwrap on a Boundary: {reason:?}")
61 }
62 }
63 }
64
65 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Value<U, B> {
67 match self {
68 Value::Interior(v) => Value::Interior(f(v)),
69 Value::Boundary { reason, last } => Value::Boundary {
70 reason,
71 last: f(last),
72 },
73 }
74 }
75
76 pub fn propagate(self) -> Result<T, B> {
80 match self {
81 Value::Interior(v) => Ok(v),
82 Value::Boundary { reason, .. } => Err(reason),
83 }
84 }
85
86 pub fn trace(self, chain: &mut Chain, label: &'static str) -> Result<T, B>
90 where
91 T: std::fmt::Debug,
92 {
93 match self {
94 Value::Interior(v) => {
95 chain.record(label, &v);
96 Ok(v)
97 }
98 Value::Boundary { reason, last } => {
99 chain.record_boundary(label, &last);
100 Err(reason)
101 }
102 }
103 }
104
105 pub fn and_then<U: Default>(self, f: impl FnOnce(T) -> Value<U, B>) -> Value<U, B> {
108 match self {
109 Value::Interior(v) => f(v),
110 Value::Boundary { reason, .. } => Value::Boundary {
111 reason,
112 last: U::default(),
113 },
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
122pub struct ChainEntry {
123 pub label: &'static str,
125 pub value: String,
127 pub is_boundary: bool,
129}
130
131#[derive(Debug, Clone, Default)]
137pub struct Chain {
138 entries: Vec<ChainEntry>,
139}
140
141impl Chain {
142 pub fn new() -> Self {
143 Chain { entries: Vec::new() }
144 }
145
146 pub fn record(&mut self, label: &'static str, value: &dyn fmt::Debug) {
148 self.entries.push(ChainEntry {
149 label,
150 value: format!("{value:?}"),
151 is_boundary: false,
152 });
153 }
154
155 pub fn record_boundary(&mut self, label: &'static str, value: &dyn fmt::Debug) {
157 self.entries.push(ChainEntry {
158 label,
159 value: format!("{value:?}"),
160 is_boundary: true,
161 });
162 }
163
164 pub fn entries(&self) -> &[ChainEntry] {
166 &self.entries
167 }
168
169 pub fn depth(&self) -> usize {
171 self.entries.iter().filter(|e| !e.is_boundary).count()
172 }
173}
174
175impl fmt::Display for Chain {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 for (i, entry) in self.entries.iter().enumerate() {
178 let marker = if entry.is_boundary { "✗" } else { "✓" };
179 write!(f, " {marker} [{}] {}: {}", i + 1, entry.label, entry.value)?;
180 if i < self.entries.len() - 1 {
181 writeln!(f)?;
182 }
183 }
184 Ok(())
185 }
186}
187
188#[derive(Debug, Clone)]
191pub struct GenericBoundary;
192impl BoundaryKind for GenericBoundary {}
193
194#[derive(Debug, Clone)]
196pub struct DivisionByZero;
197impl BoundaryKind for DivisionByZero {}
198
199#[derive(Debug, Clone)]
200pub struct Overflow;
201impl BoundaryKind for Overflow {}
202
203#[derive(Debug, Clone)]
204pub struct Underflow;
205impl BoundaryKind for Underflow {}
206
207#[derive(Debug, Clone)]
208pub struct Imaginary { pub real_part: f64 }
209impl BoundaryKind for Imaginary {}
210
211#[derive(Debug, Clone)]
213pub struct ModelUncertain { pub confidence: f64 }
214impl BoundaryKind for ModelUncertain {}
215
216#[derive(Debug, Clone)]
217pub struct Hallucinated { pub confidence: f64, pub grounding: Option<String> }
218impl BoundaryKind for Hallucinated {}
219
220#[derive(Debug, Clone)]
221pub struct Contradicted { pub sources: Vec<String>, pub claim: String }
222impl BoundaryKind for Contradicted {}
223
224pub fn divide(a: f64, b: f64) -> Value<f64, DivisionByZero> {
235 if b == 0.0 {
236 Value::Boundary { reason: DivisionByZero, last: a }
237 } else {
238 Value::Interior(a / b)
239 }
240}
241
242pub fn sqrt(a: f64) -> Value<f64, Imaginary> {
244 if a < 0.0 {
245 Value::Boundary { reason: Imaginary { real_part: a }, last: 0.0 }
246 } else {
247 Value::Interior(a.sqrt())
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn division_interior() {
257 let result = divide(10.0, 2.0);
258 assert!(result.is_interior());
259 assert_eq!(result.unwrap(), 5.0);
260 }
261
262 #[test]
263 fn division_boundary() {
264 let result = divide(10.0, 0.0);
265 assert!(result.is_boundary());
266 match result {
267 Value::Boundary { reason: DivisionByZero, last } => {
268 assert_eq!(last, 10.0);
269 }
270 _ => panic!("expected boundary"),
271 }
272 }
273
274 #[test]
275 fn division_or_fallback() {
276 assert_eq!(divide(10.0, 0.0).or(0.0), 0.0);
277 }
278
279 #[test]
280 fn sqrt_interior() {
281 assert_eq!(sqrt(9.0).unwrap(), 3.0);
282 }
283
284 #[test]
285 fn sqrt_boundary() {
286 let result = sqrt(-1.0);
287 assert!(result.is_boundary());
288 }
289
290 #[test]
291 fn map_propagates_boundary() {
292 let result = divide(10.0, 0.0).map(|v| v * 2.0);
293 assert!(result.is_boundary());
294 }
295
296 #[test]
297 fn map_transforms_interior() {
298 let result = divide(10.0, 2.0).map(|v| v * 2.0);
299 assert_eq!(result.unwrap(), 10.0);
300 }
301
302 #[test]
303 fn model_uncertain() {
304 let result: Value<String, ModelUncertain> = Value::Boundary {
305 reason: ModelUncertain { confidence: 0.3 },
306 last: "Paris is the capital of France".to_string(),
307 };
308 assert!(result.is_boundary());
309 }
310
311 #[test]
312 fn hallucinated() {
313 let result: Value<String, Hallucinated> = Value::Boundary {
314 reason: Hallucinated { confidence: 0.1, grounding: None },
315 last: "The moon is made of cheese".to_string(),
316 };
317 match result {
318 Value::Boundary { reason, .. } => assert!(reason.grounding.is_none()),
319 _ => panic!("expected boundary"),
320 }
321 }
322
323 #[test]
324 fn and_then_interior() {
325 let result = divide(10.0, 2.0).and_then(|v| divide(v, 2.0));
326 assert!(result.is_interior());
327 assert_eq!(result.unwrap(), 2.5);
328 }
329
330 #[test]
331 fn and_then_propagates_boundary() {
332 let result = divide(10.0, 0.0).and_then(|v| divide(v, 2.0));
333 assert!(result.is_boundary());
334 }
335
336 #[boundary_check]
339 fn safe_divide(a: f64, b: f64) -> f64 {
340 divide(a, b).or(0.0)
341 }
342
343 #[test]
344 fn boundary_check_allows_or() {
345 assert_eq!(safe_divide(10.0, 2.0), 5.0);
346 assert_eq!(safe_divide(10.0, 0.0), 0.0);
347 }
348
349 #[boundary_check]
350 fn safe_match(a: f64, b: f64) -> f64 {
351 match divide(a, b) {
352 Value::Interior(v) => v,
353 Value::Boundary { reason: DivisionByZero, last } => last,
354 }
355 }
356
357 #[test]
358 fn boundary_check_allows_match() {
359 assert_eq!(safe_match(10.0, 2.0), 5.0);
360 assert_eq!(safe_match(10.0, 0.0), 10.0);
361 }
362
363 #[derive(Debug, Clone, BoundaryKind)]
366 #[boundary_kind(crate_path = "crate")]
367 struct InsufficientFunds {
368 balance: f64,
369 required: f64,
370 }
371
372 #[test]
373 fn user_defined_boundary() {
374 let result: Value<f64, InsufficientFunds> = Value::Boundary {
375 reason: InsufficientFunds { balance: 50.0, required: 100.0 },
376 last: 50.0,
377 };
378 assert!(result.is_boundary());
379 match result {
380 Value::Boundary { reason, .. } => {
381 assert_eq!(reason.balance, 50.0);
382 assert_eq!(reason.required, 100.0);
383 }
384 _ => panic!("expected boundary"),
385 }
386 }
387}