pact_ffi 0.4.0

Pact interface for foreign languages.
Documentation
//! XML matching support

use pact_models::matchingrules::{MatchingRuleCategory};
use serde_json::Value::Number;
use sxd_document::dom::{Document, Element, ChildOfElement, Text};
use serde_json::Value;
use serde_json::map::Map;
use sxd_document::Package;
use sxd_document::writer::format_document;
use pact_models::matchingrules::{RuleLogic};
use pact_models::generators::{Generators, GeneratorCategory, Generator};
use pact_models::json_utils::json_to_string;
use log::*;
use std::collections::HashMap;
use maplit::hashmap;
use either::Either;
use pact_models::path_exp::DocPath;

#[allow(deprecated)]
use crate::mock_server::bodies::matcher_from_integration_json;

pub fn generate_xml_body(attributes: &Map<String, Value>, matching_rules: &mut MatchingRuleCategory, generators: &mut Generators) -> Result<Vec<u8>, String> {
  let package = Package::new();
  let doc = package.as_document();

  trace!("generate_xml_body: attributes = {:?}", attributes);
  match attributes.get("root") {
    Some(val) => match val {
      Value::Object(obj) => {
        match create_element_from_json(doc, None, obj, matching_rules, generators, &vec!["$"], false, &mut hashmap!{}) {
          Either::Left(element) => doc.root().append_child(element),
          Either::Right(_) => warn!("Can't append text node to the root")
        }
      },
      _ => {
        warn!("Root XML element is not an object: {}", val);
      }
    },
    None => {
      warn!("No Root XML element");
    }
  }

  debug!("Done processing XML body");
  let mut output = vec![];
  match format_document(&doc, &mut output) {
    Ok(_) => Ok(output),
    Err(err) => Err(format!("Unable to generate a valid XML document: {}", err) )
  }
}

fn create_element_from_json<'a>(
  doc: Document<'a>,
  parent: Option<Element<'a>>,
  object: &Map<String, Value>,
  matching_rules: &mut MatchingRuleCategory,
  generators: &mut Generators,
  path: &Vec<&str>,
  _type_matcher: bool,
  namespaces: &mut HashMap<String, String>
) -> Either<Element<'a>, Text<'a>> {
  trace!("create_element_from_json {:?}: object = {:?}", path, object);
  let mut element = None;
  let mut text = None;
  if object.contains_key("pact:matcher:type") {
    let mut updated_path = path.clone();
    if let Some(val) = object.get("value") {
      if let Value::Object(attr) = val {
        let name = json_to_string(attr.get("name").unwrap());
        updated_path.push(&name);
        let doc_path = DocPath::new(updated_path.join(".").to_string()).unwrap_or(DocPath::root());

        #[allow(deprecated)]
        if let Some(rule) = matcher_from_integration_json(object) {
          matching_rules.add_rule(doc_path.clone(), rule, RuleLogic::And);
        }
        if let Some(gen) = object.get("pact:generator:type") {
          match Generator::from_map(&json_to_string(gen), object) {
            Some(generator) => generators.add_generator_with_subcategory(&GeneratorCategory::BODY, doc_path, generator),
            _ => ()
          };
        }

        let new_element = doc.create_element(name.as_str());
        if let Some(attributes) = val.get("attributes") {
          match attributes {
            Value::Object(attributes) => {
              let mut updated_path = path.clone();
              updated_path.push(&name);
              add_attributes(&new_element, attributes, matching_rules, generators, &updated_path);
            },
            _ => ()
          }
        };

        if let Some(children) = val.get("children") {
          match children {
            Value::Array(children) => for child in children {
              match child {
                Value::Object(attributes) => {
                  let mut updated_path = path.clone();
                  updated_path.push(new_element.name().local_part());
                  create_element_from_json(doc, Some(new_element), attributes, matching_rules, generators, &updated_path, true, namespaces);
                },
                _ => panic!("Intermediate JSON format is invalid, child is not an object: {:?}", child)
              }
            },
            _ => panic!("Intermediate JSON format is invalid, children is not an Array: {:?}", children)
          }
        };

        element = Some(new_element)
      } else {
        panic!("Intermediate JSON format is invalid, corresponding value for the given matcher was not an object: {:?}", object)
      }
    } else {
      panic!("Intermediate JSON format is invalid, no corresponding value for the given matcher: {:?}", object)
    }
  } else if let Some(content) = object.get("content") {
    text = Some(doc.create_text(json_to_string(content).as_str()));
    if let Some(matcher) = object.get("matcher") {
      let mut text_path = path.clone();
      text_path.push("#text");
      let doc_path = DocPath::new(&text_path.join(".")).unwrap_or(DocPath::root());

      if let Value::Object(matcher) = matcher {
        #[allow(deprecated)]
        if let Some(rule) = matcher_from_integration_json(matcher) {
          matching_rules.add_rule(doc_path.clone(), rule, RuleLogic::And);
        }
      }
      if let Some(gen) = object.get("pact:generator:type") {
        if let Value::Object(matcher) = matcher {
          match Generator::from_map(&json_to_string(gen), matcher) {
            Some(generator) => generators.add_generator_with_subcategory(&GeneratorCategory::BODY, doc_path, generator),
            _ => ()
          };
        }
      }
    }
  } else if let Some(name) = object.get("name") {
    let name = json_to_string(name);
    let new_element = doc.create_element(name.as_str());
    if let Some(attributes) = object.get("attributes") {
      match attributes {
        Value::Object(attributes) => {
          let mut updated_path = path.clone();
          updated_path.push(&name);
          add_attributes(&new_element, attributes, matching_rules, generators, &updated_path);
        },
        _ => ()
      }
    };

    if let Some(children) = object.get("children") {
      match children {
        Value::Array(children) => for child in children {
          match child {
            Value::Object(attributes) => {
              let mut updated_path = path.clone();
              updated_path.push(&name);
              create_element_from_json(doc, Some(new_element), attributes, matching_rules, generators, &updated_path, false, namespaces);
            },
            _ => panic!("Intermediate JSON format is invalid, child is not an object: {:?}", child)
          }
        },
        _ => panic!("Intermediate JSON format is invalid, children is not an Array: {:?}", children)
      }
    };
    element = Some(new_element);
  } else {
    panic!("Ignoring invalid object {:?}", object);
  };

  if let Some(parent) = parent {
    let examples = match object.get("examples") {
      Some(val) => match val {
        Number(val) => val.as_u64().unwrap(),
        _ => 1
      }
      None => 1
    };

    if let Some(element) = element {
      for _ in 0..examples {
        parent.append_child(duplicate_element(doc, &element));
      }
    } else if let Some(text) = text {
      parent.append_child(&text);
    }
  }

  if let Some(element) = element {
    Either::Left(element)
  } else if let Some(text) = text {
    Either::Right(text)
  } else {
    panic!("Intermediate JSON was mapped to neither an element or a text node")
  }
}

fn add_attributes(
  element: &Element,
  attributes: &Map<String, Value>,
  matching_rules: &mut MatchingRuleCategory,
  generators: &mut Generators,
  path: &Vec<&str>
) {
  trace!("add_attributes: attributes = {:?}", attributes);
  for (k, v) in attributes {
    let path = format!("{}['@{}']", path.join("."), k);

    let value = match v {
      Value::Object(matcher_definition) => if matcher_definition.contains_key("pact:matcher:type") {
        let doc_path = DocPath::new(path).unwrap_or(DocPath::root());
        #[allow(deprecated)]
        if let Some(rule) = matcher_from_integration_json(matcher_definition) {
          matching_rules.add_rule(doc_path.clone(), rule, RuleLogic::And);
        }
        if let Some(gen) = matcher_definition.get("pact:generator:type") {
          match Generator::from_map(&json_to_string(gen), matcher_definition) {
            Some(generator) => generators.add_generator_with_subcategory(&GeneratorCategory::BODY, doc_path, generator),
            _ => ()
          };
        }
        json_to_string(matcher_definition.get("value").unwrap())
      } else {
        json_to_string(&v)
      },
      _ => json_to_string(&v)
    };

    trace!("add_attributes: setting attribute key {}, value {}", k.as_str(), value.as_str());

    element.set_attribute_value(k.as_str(), value.as_str());
  }
}

fn duplicate_element<'a>(doc: Document<'a>, el: &Element<'a>) -> Element<'a> {
  let element = doc.create_element(el.name());
  for attr in el.attributes() {
    element.set_attribute_value(attr.name(), attr.value());
  }
  for child in el.children() {
    match child {
      ChildOfElement::Element(el) => element.append_child(duplicate_element(doc, &el)),
      ChildOfElement::Text(txt) => element.append_child(txt),
      _ => ()
    }
  }
  element
}