use std::collections::BTreeMap;
use std::sync::Arc;
use crate::context::{CamelContext, Registry};
use crate::error::{CamelError, Result};
use crate::message::Exchange;
pub mod base64;
pub mod jacksonxml;
pub mod compress;
pub mod codecs;
pub mod csv;
pub mod formats;
pub trait DataFormat: Send + Sync {
fn name(&self) -> &str;
fn marshal(&self, exchange: &mut Exchange) -> Result<()>;
fn unmarshal(&self, exchange: &mut Exchange) -> Result<()>;
}
pub fn bind_data_format(registry: &Registry, name: impl Into<String>, df: impl DataFormat + 'static) {
let df: Arc<dyn DataFormat> = Arc::new(df);
registry.bind(name, df);
}
pub fn lookup_data_format(registry: &Registry, name: &str) -> Option<Arc<dyn DataFormat>> {
registry.lookup::<Arc<dyn DataFormat>>(name).map(|a| (*a).clone())
}
pub type DataFormatFactory = dyn Fn(&BTreeMap<String, String>) -> Result<Arc<dyn DataFormat>> + Send + Sync;
struct FactoryHolder(Arc<DataFormatFactory>);
const FACTORY_PREFIX: &str = "camel.dataformat.factory.";
pub fn register_data_format_factory(
registry: &Registry,
name: &str,
factory: impl Fn(&BTreeMap<String, String>) -> Result<Arc<dyn DataFormat>> + Send + Sync + 'static,
) {
registry.bind(format!("{}{}", FACTORY_PREFIX, name), FactoryHolder(Arc::new(factory)));
}
pub fn lookup_data_format_factory(registry: &Registry, name: &str) -> Option<Arc<DataFormatFactory>> {
registry.lookup::<FactoryHolder>(&format!("{}{}", FACTORY_PREFIX, name)).map(|h| h.0.clone())
}
pub fn create_data_format_with(registry: &Registry, name: &str, o: &BTreeMap<String, String>) -> Result<Arc<dyn DataFormat>> {
match lookup_data_format_factory(registry, name) {
Some(f) => f(o),
None => create_data_format(name, o),
}
}
fn opt<'a>(o: &'a BTreeMap<String, String>, keys: &[&str]) -> Option<&'a str> {
keys.iter().find_map(|k| o.get(*k).map(|s| s.as_str()))
}
fn opt_bool(o: &BTreeMap<String, String>, keys: &[&str], default: bool) -> Result<bool> {
match opt(o, keys) {
None => Ok(default),
Some(v) => match v.to_ascii_lowercase().as_str() {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(CamelError::illegal_argument(format!("Option {} must be true or false but was: {}", keys[0], v))),
},
}
}
fn opt_num(o: &BTreeMap<String, String>, keys: &[&str], default: i64) -> Result<i64> {
match opt(o, keys) {
None => Ok(default),
Some(v) => v.trim().parse::<i64>().map_err(|_| CamelError::illegal_argument(format!("Option {} must be a number but was: {}", keys[0], v))),
}
}
fn level(o: &BTreeMap<String, String>) -> Result<u32> {
let l = opt_num(o, &["compressionLevel", "level"], 6)?;
Ok(if l < 0 { 6 } else { l.min(9) as u32 })
}
pub fn create_data_format(name: &str, o: &BTreeMap<String, String>) -> Result<Arc<dyn DataFormat>> {
Ok(match name {
"json" | "jackson" | "gson" | "jsonb" | "fastjson" | "johnzon" => Arc::new(formats::JsonDataFormat {
pretty_print: opt_bool(o, &["prettyPrint", "pretty_print"], false)?,
content_type_header: opt_bool(o, &["contentTypeHeader"], true)?,
}),
"csv" => {
let mut c = csv::CsvDataFormat::default();
if let Some(d) = opt(o, &["delimiter"]) {
c.delimiter = csv::parse_char_option(d).unwrap_or(',');
}
if let Some(q) = opt(o, &["quote"]) {
c.quote = csv::parse_char_option(q);
}
if opt_bool(o, &["quoteDisabled"], false)? {
c.quote = None;
}
if let Some(e) = opt(o, &["escape"]) {
c.escape = csv::parse_char_option(e);
}
if let Some(r) = opt(o, &["recordSeparator"]) {
c.record_separator = r.replace("\\r", "\r").replace("\\n", "\n");
}
if let Some(h) = opt(o, &["header"]) {
c.header = h.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect();
}
c.skip_header_record = opt_bool(o, &["skipHeaderRecord"], false)?;
c.use_maps = opt_bool(o, &["useMaps"], false)? || opt_bool(o, &["useOrderedMaps"], false)?;
c.ignore_empty_lines = opt_bool(o, &["ignoreEmptyLines"], true)?;
c.trim = opt_bool(o, &["ignoreSurroundingSpaces", "trim"], false)?;
if let Some(q) = opt(o, &["quoteMode"]) {
c.quote_mode = q.to_string();
}
c.null_string = opt(o, &["nullString"]).map(|s| s.to_string());
c.comment_marker = opt(o, &["commentMarker"]).and_then(csv::parse_char_option);
Arc::new(c)
}
"base64" => Arc::new(base64::Base64DataFormat {
line_length: opt_num(o, &["lineLength"], 76)?.max(0) as usize,
line_separator: opt(o, &["lineSeparator"]).map(|s| s.replace("\\r", "\r").replace("\\n", "\n")).unwrap_or_else(|| "\r\n".into()),
url_safe: opt_bool(o, &["urlSafe"], false)?,
}),
"string" => Arc::new(formats::StringDataFormat { charset: opt(o, &["charset"]).unwrap_or("UTF-8").to_string() }),
"hex" => Arc::new(formats::HexDataFormat { upper_case: opt_bool(o, &["upperCase"], false)? }),
"gzipDeflater" | "gzipdeflater" | "gzip" => Arc::new(compress::GzipDataFormat { level: level(o)? }),
"zipDeflater" | "zipdeflater" | "zip" | "deflate" | "zlib" => Arc::new(compress::ZlibDataFormat { level: level(o)? }),
"zipFile" | "zipfile" => Arc::new(compress::ZipFileDataFormat { level: level(o)?, using_iterator: opt_bool(o, &["usingIterator"], false)? }),
"jacksonXml" | "jacksonxml" | "xml" | "jaxb" => Arc::new(jacksonxml::XmlDataFormat::from_options(o)?),
"urlencoded" => Arc::new(formats::UrlEncodedDataFormat),
other => return Err(CamelError::illegal_argument(format!("Cannot find data format with name: {}", other))),
})
}
#[derive(Clone)]
pub struct DataFormatDefinition {
pub name: String,
pub options: BTreeMap<String, String>,
pub instance: Option<Arc<dyn DataFormat>>,
}
impl std::fmt::Debug for DataFormatDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)?;
if !self.options.is_empty() {
write!(f, "{:?}", self.options)?;
}
Ok(())
}
}
impl DataFormatDefinition {
pub fn new(name: impl Into<String>) -> Self {
DataFormatDefinition { name: name.into(), options: BTreeMap::new(), instance: None }
}
pub fn named(name: impl Into<String>) -> Self {
Self::new(name)
}
pub fn custom(df: impl DataFormat + 'static) -> Self {
let name = df.name().to_string();
DataFormatDefinition { name, options: BTreeMap::new(), instance: Some(Arc::new(df)) }
}
pub fn json() -> Self {
Self::new("json")
}
pub fn csv() -> Self {
Self::new("csv")
}
pub fn base64() -> Self {
Self::new("base64")
}
pub fn string() -> Self {
Self::new("string")
}
pub fn hex() -> Self {
Self::new("hex")
}
pub fn gzip() -> Self {
Self::new("gzipDeflater")
}
pub fn zip_deflater() -> Self {
Self::new("zipDeflater")
}
pub fn zip_file() -> Self {
Self::new("zipFile")
}
pub fn xml() -> Self {
Self::new("jacksonXml")
}
pub fn urlencoded() -> Self {
Self::new("urlencoded")
}
pub fn option(mut self, key: impl Into<String>, value: impl ToString) -> Self {
self.options.insert(key.into(), value.to_string());
self
}
pub fn pretty_print(self, b: bool) -> Self {
self.option("prettyPrint", b)
}
pub fn delimiter(self, c: char) -> Self {
self.option("delimiter", c)
}
pub fn quote(self, c: char) -> Self {
self.option("quote", c)
}
pub fn use_maps(self, b: bool) -> Self {
self.option("useMaps", b)
}
pub fn use_ordered_maps(self, b: bool) -> Self {
self.option("useOrderedMaps", b)
}
pub fn skip_header_record(self, b: bool) -> Self {
self.option("skipHeaderRecord", b)
}
pub fn header(self, names: &[&str]) -> Self {
self.option("header", names.join(","))
}
pub fn line_length(self, n: usize) -> Self {
self.option("lineLength", n)
}
pub fn url_safe(self, b: bool) -> Self {
self.option("urlSafe", b)
}
pub fn charset(self, cs: &str) -> Self {
self.option("charset", cs)
}
pub fn root_name(self, n: &str) -> Self {
self.option("rootName", n)
}
pub fn compression_level(self, l: u32) -> Self {
self.option("compressionLevel", l)
}
pub fn resolve(&self, ctx: &CamelContext) -> Result<Arc<dyn DataFormat>> {
if let Some(i) = &self.instance {
return Ok(i.clone());
}
if let Some(df) = lookup_data_format(ctx.registry(), &self.name) {
return Ok(df);
}
let mut opts = BTreeMap::new();
for (k, v) in &self.options {
opts.insert(k.clone(), ctx.resolve_placeholders(v)?);
}
create_data_format_with(ctx.registry(), &self.name, &opts)
}
}
impl From<&str> for DataFormatDefinition {
fn from(s: &str) -> Self {
DataFormatDefinition::new(s)
}
}
impl From<String> for DataFormatDefinition {
fn from(s: String) -> Self {
DataFormatDefinition::new(s)
}
}