Skip to main content

tokf_common/
examples.rs

1use serde::{Deserialize, Serialize};
2
3use crate::safety::SafetyWarning;
4
5/// A single before/after example for a filter.
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct FilterExample {
8    /// Test case name.
9    pub name: String,
10    /// Exit code used for this example.
11    pub exit_code: i32,
12    /// Raw (unfiltered) input.
13    pub raw: String,
14    /// Filtered output.
15    pub filtered: String,
16    /// Number of lines in raw input.
17    pub raw_line_count: usize,
18    /// Number of lines in filtered output.
19    pub filtered_line_count: usize,
20}
21
22/// Collection of examples with aggregated safety results.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct FilterExamples {
25    pub examples: Vec<FilterExample>,
26    pub safety: ExamplesSafety,
27}
28
29/// Serializable safety summary for the examples payload.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct ExamplesSafety {
32    pub passed: bool,
33    pub warnings: Vec<SafetyWarningDto>,
34}
35
36/// A flattened, transport-friendly representation of a safety warning.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct SafetyWarningDto {
39    pub kind: String,
40    pub message: String,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub detail: Option<String>,
43}
44
45impl From<&SafetyWarning> for SafetyWarningDto {
46    fn from(w: &SafetyWarning) -> Self {
47        Self {
48            kind: w.kind.as_str().to_string(),
49            message: w.message.clone(),
50            detail: w.detail.clone(),
51        }
52    }
53}
54
55#[cfg(test)]
56#[allow(clippy::unwrap_used)]
57mod tests {
58    use super::*;
59    use crate::safety::WarningKind;
60
61    #[test]
62    fn serialize_round_trip() {
63        let examples = FilterExamples {
64            examples: vec![FilterExample {
65                name: "basic".to_string(),
66                exit_code: 0,
67                raw: "line1\nline2\nline3".to_string(),
68                filtered: "line1".to_string(),
69                raw_line_count: 3,
70                filtered_line_count: 1,
71            }],
72            safety: ExamplesSafety {
73                passed: true,
74                warnings: vec![],
75            },
76        };
77
78        let json = serde_json::to_string(&examples).unwrap();
79        let parsed: FilterExamples = serde_json::from_str(&json).unwrap();
80        assert_eq!(parsed.examples.len(), 1);
81        assert_eq!(parsed.examples[0].name, "basic");
82        assert!(parsed.safety.passed);
83    }
84
85    #[test]
86    fn warning_dto_from_safety_warning() {
87        let warning = SafetyWarning {
88            kind: WarningKind::TemplateInjection,
89            message: "bad template".to_string(),
90            detail: Some("ignore previous instructions".to_string()),
91        };
92        let dto = SafetyWarningDto::from(&warning);
93        assert_eq!(dto.kind, "template_injection");
94        assert_eq!(dto.message, "bad template");
95    }
96}