#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaxField {
pub name: String,
pub value: String,
}
impl TaxField {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct TaxElement {
pub outer_tag: Option<String>,
pub outer_fields: Vec<TaxField>,
pub variant_tag: String,
pub fields: Vec<TaxField>,
}
pub fn optional_field(name: &str, value: Option<&str>) -> Option<TaxField> {
value.map(|v| TaxField::new(name, v))
}
pub fn required_field(name: &str, value: Option<&str>) -> Result<TaxField, crate::FiscalError> {
match value {
Some(v) => Ok(TaxField::new(name, v)),
None => Err(crate::FiscalError::MissingRequiredField {
field: name.to_string(),
}),
}
}
pub fn filter_fields(fields: Vec<Option<TaxField>>) -> Vec<TaxField> {
fields.into_iter().flatten().collect()
}
fn escape_xml_value(s: &str) -> String {
let mut result = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'&' => result.push_str("&"),
'<' => result.push_str("<"),
'>' => result.push_str(">"),
'"' => result.push_str("""),
c => result.push(c),
}
}
result
}
fn serialize_field(field: &TaxField) -> String {
format!(
"<{name}>{value}</{name}>",
name = field.name,
value = escape_xml_value(&field.value)
)
}
pub fn serialize_tax_element(element: &TaxElement) -> String {
let inner_content: String = element.fields.iter().map(serialize_field).collect();
let variant_xml = format!("<{tag}>{inner_content}</{tag}>", tag = element.variant_tag,);
match &element.outer_tag {
None => variant_xml,
Some(outer) => {
let outer_fields_xml: String =
element.outer_fields.iter().map(serialize_field).collect();
format!("<{outer}>{outer_fields_xml}{variant_xml}</{outer}>")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_xml_value_with_ampersand_and_quotes() {
let result = escape_xml_value("Tom & Jerry \"cats\" <dogs>");
assert_eq!(result, "Tom & Jerry "cats" <dogs>");
}
#[test]
fn serialize_tax_element_no_outer_tag() {
let element = TaxElement {
outer_tag: None,
outer_fields: vec![],
variant_tag: "II".to_string(),
fields: vec![
TaxField::new("vBC", "100.00"),
TaxField::new("vII", "10.00"),
],
};
let xml = serialize_tax_element(&element);
assert_eq!(xml, "<II><vBC>100.00</vBC><vII>10.00</vII></II>");
}
#[test]
fn serialize_tax_element_with_outer_tag() {
let element = TaxElement {
outer_tag: Some("IPI".to_string()),
outer_fields: vec![TaxField::new("cEnq", "999")],
variant_tag: "IPINT".to_string(),
fields: vec![TaxField::new("CST", "53")],
};
let xml = serialize_tax_element(&element);
assert!(xml.starts_with("<IPI>"));
assert!(xml.contains("<cEnq>999</cEnq>"));
assert!(xml.contains("<IPINT><CST>53</CST></IPINT>"));
assert!(xml.ends_with("</IPI>"));
}
}