Skip to main content

brainwires_proxy/convert/
detect.rs

1//! Format detection implementations.
2
3use crate::convert::FormatDetector;
4use crate::types::FormatId;
5
6/// Detects JSON format by checking for known field names in the body.
7pub struct JsonFieldDetector {
8    /// Format ID to return when detection succeeds.
9    format_id: FormatId,
10    /// Field names to look for in the JSON. If any are found, detection succeeds.
11    fields: Vec<String>,
12}
13
14impl JsonFieldDetector {
15    pub fn new(format_id: FormatId, fields: Vec<String>) -> Self {
16        Self { format_id, fields }
17    }
18}
19
20impl FormatDetector for JsonFieldDetector {
21    fn detect(&self, body: &[u8], content_type: Option<&str>) -> Option<FormatId> {
22        // Quick content-type check
23        if let Some(ct) = content_type
24            && !ct.contains("json")
25            && !ct.contains("text")
26        {
27            return None;
28        }
29
30        // Try to parse as JSON
31        let value: serde_json::Value = serde_json::from_slice(body).ok()?;
32        let obj = value.as_object()?;
33
34        for field in &self.fields {
35            if obj.contains_key(field) {
36                return Some(self.format_id.clone());
37            }
38        }
39
40        None
41    }
42
43    fn name(&self) -> &str {
44        "json_field_detector"
45    }
46}
47
48/// Generic JSON detector — matches any valid JSON body.
49#[derive(Default)]
50pub struct GenericJsonDetector;
51
52impl FormatDetector for GenericJsonDetector {
53    fn detect(&self, body: &[u8], content_type: Option<&str>) -> Option<FormatId> {
54        if let Some(ct) = content_type
55            && ct.contains("json")
56        {
57            return Some(FormatId::new("json"));
58        }
59
60        // Try parsing
61        if serde_json::from_slice::<serde_json::Value>(body).is_ok() {
62            Some(FormatId::new("json"))
63        } else {
64            None
65        }
66    }
67
68    fn name(&self) -> &str {
69        "generic_json"
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn json_field_detector_matches() {
79        let detector = JsonFieldDetector::new(
80            FormatId::new("openai"),
81            vec!["model".into(), "messages".into()],
82        );
83
84        let body = br#"{"model": "gpt-4", "messages": []}"#;
85        let result = detector.detect(body, Some("application/json"));
86        assert_eq!(result, Some(FormatId::new("openai")));
87    }
88
89    #[test]
90    fn json_field_detector_no_match() {
91        let detector = JsonFieldDetector::new(FormatId::new("openai"), vec!["model".into()]);
92
93        let body = br#"{"data": "something else"}"#;
94        let result = detector.detect(body, Some("application/json"));
95        assert!(result.is_none());
96    }
97
98    #[test]
99    fn json_field_detector_rejects_non_json_content_type() {
100        let detector = JsonFieldDetector::new(FormatId::new("openai"), vec!["model".into()]);
101
102        let body = br#"{"model": "gpt-4"}"#;
103        let result = detector.detect(body, Some("application/xml"));
104        assert!(result.is_none());
105    }
106
107    #[test]
108    fn json_field_detector_invalid_json() {
109        let detector = JsonFieldDetector::new(FormatId::new("test"), vec!["key".into()]);
110
111        let body = b"not json at all";
112        let result = detector.detect(body, Some("application/json"));
113        assert!(result.is_none());
114    }
115
116    #[test]
117    fn generic_json_detector_by_content_type() {
118        let detector = GenericJsonDetector;
119        let body = b"not valid json";
120        let result = detector.detect(body, Some("application/json"));
121        assert_eq!(result, Some(FormatId::new("json")));
122    }
123
124    #[test]
125    fn generic_json_detector_by_parsing() {
126        let detector = GenericJsonDetector;
127        let body = br#"{"key": "value"}"#;
128        let result = detector.detect(body, None);
129        assert_eq!(result, Some(FormatId::new("json")));
130    }
131
132    #[test]
133    fn generic_json_detector_no_match() {
134        let detector = GenericJsonDetector;
135        let body = b"plain text";
136        let result = detector.detect(body, Some("text/plain"));
137        assert!(result.is_none());
138    }
139}