Skip to main content

cruxx_script/
handler_output.rs

1/// Output from a pipeline handler — value plus optional confidence score.
2use serde_json::Value;
3
4/// Carries the handler's output value and an optional confidence score.
5///
6/// Handlers that do not have a meaningful confidence score return `None`; the
7/// runner treats that as `1.0` via [`HandlerOutput::confidence_or_default`].
8#[derive(Debug, Clone)]
9pub struct HandlerOutput {
10    pub value: Value,
11    pub confidence: Option<f32>,
12}
13
14impl HandlerOutput {
15    pub fn new(value: Value) -> Self {
16        Self {
17            value,
18            confidence: None,
19        }
20    }
21
22    /// Constructs a `HandlerOutput` with a validated confidence score.
23    ///
24    /// - NaN is treated as absent confidence (`None`).
25    /// - Values outside `[0.0, 1.0]` are clamped to the nearest bound.
26    pub fn with_confidence(value: Value, confidence: f32) -> Self {
27        let confidence = if confidence.is_nan() {
28            None
29        } else {
30            Some(confidence.clamp(0.0, 1.0))
31        };
32        Self { value, confidence }
33    }
34
35    /// Returns the confidence score, defaulting to `1.0` when absent.
36    pub fn confidence_or_default(&self) -> f32 {
37        self.confidence.unwrap_or(1.0)
38    }
39}
40
41impl std::ops::Deref for HandlerOutput {
42    type Target = Value;
43
44    fn deref(&self) -> &Self::Target {
45        &self.value
46    }
47}
48
49impl PartialEq<Value> for HandlerOutput {
50    fn eq(&self, other: &Value) -> bool {
51        &self.value == other
52    }
53}
54
55impl From<Value> for HandlerOutput {
56    fn from(value: Value) -> Self {
57        Self::new(value)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use serde_json::json;
65
66    #[test]
67    fn from_value_has_no_confidence() {
68        let out = HandlerOutput::from(json!({ "x": 1 }));
69        assert!(out.confidence.is_none());
70        assert_eq!(out.confidence_or_default(), 1.0);
71    }
72
73    #[test]
74    fn with_confidence_stores_score() {
75        let out = HandlerOutput::with_confidence(json!("ok"), 0.75);
76        assert_eq!(out.confidence, Some(0.75));
77        assert_eq!(out.confidence_or_default(), 0.75);
78    }
79
80    #[test]
81    fn nan_confidence_becomes_none() {
82        let out = HandlerOutput::with_confidence(json!("x"), f32::NAN);
83        assert!(out.confidence.is_none());
84        assert_eq!(out.confidence_or_default(), 1.0);
85    }
86
87    #[test]
88    fn negative_confidence_clamped_to_zero() {
89        let out = HandlerOutput::with_confidence(json!("x"), -0.5);
90        assert_eq!(out.confidence, Some(0.0));
91    }
92
93    #[test]
94    fn confidence_above_one_clamped_to_one() {
95        let out = HandlerOutput::with_confidence(json!("x"), 1.5);
96        assert_eq!(out.confidence, Some(1.0));
97    }
98
99    #[test]
100    fn boundary_values_accepted_as_is() {
101        let lo = HandlerOutput::with_confidence(json!("x"), 0.0);
102        let hi = HandlerOutput::with_confidence(json!("x"), 1.0);
103        assert_eq!(lo.confidence, Some(0.0));
104        assert_eq!(hi.confidence, Some(1.0));
105    }
106
107    #[test]
108    fn new_is_same_as_from() {
109        let v = json!(42);
110        let a = HandlerOutput::new(v.clone());
111        let b = HandlerOutput::from(v);
112        assert!(a.confidence.is_none());
113        assert!(b.confidence.is_none());
114    }
115}