Skip to main content

camel_processor/data_format/
mod.rs

1mod csv;
2mod json;
3mod xml;
4mod zip;
5
6pub use csv::{CAMEL_CSV_HEADER_RECORD, CsvConfig, CsvDataFormat, QuoteMode, RecordSeparator};
7pub use json::{JsonConfig, JsonDataFormat};
8pub use xml::{XmlConfig, XmlDataFormat};
9pub use zip::{ZipConfig, ZipDataFormat};
10
11use camel_api::DataFormat;
12use camel_api::error::CamelError;
13use std::sync::Arc;
14
15/// Config-aware factory. `config` is the raw `config:` block from the DSL
16/// (or `Null` for defaults). Each arm deserializes into its own typed config
17/// with `deny_unknown_fields`, so a stray key fails closed with a precise message.
18pub fn builtin_data_format_with_config(
19    name: &str,
20    config: &serde_json::Value,
21) -> Result<Option<Arc<dyn DataFormat>>, CamelError> {
22    let df: Arc<dyn DataFormat> = match name {
23        "json" => Arc::new(JsonDataFormat::new(parse_cfg::<JsonConfig>(name, config)?)),
24        "csv" => Arc::new(CsvDataFormat::new(parse_cfg::<CsvConfig>(name, config)?)),
25        "xml" => Arc::new(XmlDataFormat::new(parse_cfg::<XmlConfig>(name, config)?)),
26        "zip" => Arc::new(ZipDataFormat::new(parse_cfg::<ZipConfig>(name, config)?)),
27        _ => return Ok(None),
28    };
29    Ok(Some(df))
30}
31
32/// Back-compat shim: existing callers keep working (config = Null → defaults).
33pub fn builtin_data_format(name: &str) -> Option<Arc<dyn DataFormat>> {
34    builtin_data_format_with_config(name, &serde_json::Value::Null)
35        .ok()
36        .flatten()
37}
38
39fn parse_cfg<T>(name: &str, v: &serde_json::Value) -> Result<T, CamelError>
40where
41    T: serde::de::DeserializeOwned + Default,
42{
43    if v.is_null() {
44        return Ok(T::default());
45    }
46    serde_json::from_value::<T>(v.clone()).map_err(|e| {
47        CamelError::RouteError(format!("invalid config for data format '{name}': {e}"))
48    })
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_builtin_json() {
57        let df = builtin_data_format("json").unwrap();
58        assert_eq!(df.name(), "json");
59    }
60
61    #[test]
62    fn test_builtin_xml() {
63        let df = builtin_data_format("xml").unwrap();
64        assert_eq!(df.name(), "xml");
65    }
66
67    #[test]
68    fn test_builtin_csv() {
69        let csv_df = builtin_data_format("csv").unwrap();
70        assert_eq!(csv_df.name(), "csv");
71    }
72
73    #[test]
74    fn test_builtin_unknown_returns_none() {
75        assert!(builtin_data_format("protobuf").is_none());
76        assert!(builtin_data_format("").is_none());
77    }
78
79    #[test]
80    fn test_builtin_json_with_config() {
81        let config = serde_json::json!({"max_bytes": 67108864});
82        let df = builtin_data_format_with_config("json", &config)
83            .unwrap()
84            .unwrap();
85        assert_eq!(df.name(), "json");
86    }
87
88    #[test]
89    fn test_builtin_json_with_null_config_returns_default() {
90        let df = builtin_data_format_with_config("json", &serde_json::Value::Null)
91            .unwrap()
92            .unwrap();
93        assert_eq!(df.name(), "json");
94    }
95
96    #[test]
97    fn test_builtin_json_with_unknown_key_fails() {
98        let config = serde_json::json!({"max_byte": 100});
99        let result = builtin_data_format_with_config("json", &config);
100        match result {
101            Err(CamelError::RouteError(msg)) => {
102                assert!(msg.contains("invalid config"), "msg: {msg}");
103            }
104            Err(other) => panic!("expected RouteError, got: {other:?}"),
105            Ok(_) => panic!("typo should fail closed"),
106        }
107    }
108
109    #[test]
110    fn test_builtin_shim_still_works() {
111        let df = builtin_data_format("json").unwrap();
112        assert_eq!(df.name(), "json");
113    }
114}