Skip to content

Commit

Permalink
feat: support using matchers on XML text nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Ronald Holshausen committed May 26, 2020
1 parent d92f6f5 commit b3b5e62
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 25 deletions.
1 change: 1 addition & 0 deletions examples/v3/todo-consumer/test/consumer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe("Pact V3", () => {
context("when there are a list of projects", () => {
describe("and there is a valid user session", () => {
describe("with JSON request", () => {
let provider
before(() => {
provider = new PactV3({
consumer: "TodoApp",
Expand Down
8 changes: 7 additions & 1 deletion native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,16 @@ declare_types! {
if let Some(body) = js_body {
match process_body(body, last.request.content_type_enum(), &mut last.request.matching_rules,
&mut last.request.generators) {
Ok(body) => last.request.body = body,
Ok(body) => {
debug!("Request body = {}", body.str_value());
last.request.body = body
},
Err(err) => panic!(err)
}
}
debug!("Request = {}", last.request);
debug!("Request matching rules = {:?}", last.request.matching_rules);
debug!("Request generators = {:?}", last.request.generators);
Ok(())
} else if pact.interactions.is_empty() {
Err("You need to define a new interaction with the uponReceiving method before you can define a new request with the withRequest method")
Expand Down
72 changes: 55 additions & 17 deletions native/src/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ pub fn generate_xml_body(attributes: &Map<String, Value>, matching_rules: &mut C
let package = Package::new();
let doc = package.as_document();

debug!("attributes = {:?}", attributes);
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, &"$".to_string(), false, &mut hashmap!{}) {
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(text) => warn!("Can't append text node to the root")
Either::Right(_) => warn!("Can't append text node to the root")
}
},
_ => {
Expand All @@ -48,39 +48,50 @@ fn create_element_from_json<'a>(
object: &Map<String, Value>,
matching_rules: &mut Category,
generators: &mut Generators,
path: &String,
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") {
if let Some(rule) = MatchingRule::from_integration_json(object) {
matching_rules.add_rule(path, rule, &RuleLogic::And);
matching_rules.add_rule(path.join("."), 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, path, generator),
Some(generator) => generators.add_generator_with_subcategory(&GeneratorCategory::BODY, path.join("."), generator),
_ => ()
};
}

let updated_path = path.to_owned() + ".*";
let mut updated_path = path.clone();
updated_path.push(".*");
if let Some(val) = object.get("value") {
if let Value::Object(attr) = val {
let new_element = doc.create_element(json_to_string(dbg!(attr).get("name").unwrap()).as_str());
let name = json_to_string(attr.get("name").unwrap());
let new_element = doc.create_element(name.as_str());
if let Some(attributes) = val.get("attributes") {
match attributes {
Value::Object(attributes) => add_attributes(&new_element, attributes, matching_rules, generators, &updated_path),
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 {
Value::Array(children) => for (index, child) in children.iter().enumerate() {
match child {
Value::Object(attributes) => {
let mut updated_path = path.clone();
let index = index.to_string();
updated_path.push(new_element.name().local_part());
updated_path.push(&index);
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)
Expand All @@ -98,23 +109,49 @@ fn create_element_from_json<'a>(
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()))
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.pop();
text_path.push("#text");
if let Value::Object(matcher) = matcher {
if let Some(rule) = MatchingRule::from_integration_json(matcher) {
matching_rules.add_rule(&text_path.join("."), 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, &text_path.join("."), 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) => add_attributes(&new_element, attributes, matching_rules, generators, path),
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 {
Value::Array(children) => for (index, child) in children.iter().enumerate() {
match child {
Value::Object(attributes) => {
create_element_from_json(doc, Some(new_element), attributes, matching_rules, generators, &(path.to_owned() + "." + name.as_str()), false, namespaces);
let mut updated_path = path.clone();
let index = index.to_string();
updated_path.push(&name);
updated_path.push(&index);
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)
}
Expand Down Expand Up @@ -159,10 +196,11 @@ fn add_attributes(
attributes: &Map<String, Value>,
matching_rules: &mut Category,
generators: &mut Generators,
path: &String
path: &Vec<&str>
) {
trace!("add_attributes: attributes = {:?}", attributes);
for (k, v) in attributes {
let path = format!("{}['@{}']", path, k);
let path = format!("{}['@{}']", path.join("."), k);

let value = match v {
Value::Object(matcher_definition) => if matcher_definition.contains_key("pact:matcher:type") {
Expand All @@ -182,7 +220,7 @@ fn add_attributes(
_ => json_to_string(&v)
};

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

element.set_attribute_value(k.as_str(), value.as_str());
}
Expand Down
2 changes: 1 addition & 1 deletion src/v3/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export function regex(pattern: any, str: string): any {
if (pattern instanceof RegExp) {
return {
"pact:matcher:type": "regex",
regex: pattern.toString(),
regex: pattern.source,
value: str,
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/v3/xml/xmlBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { XmlElement } from "./xmlElement"

/**
* XML Builder class for constructing XML documents with matchers
*/
export class XmlBuilder {
private root: XmlElement

Expand Down
44 changes: 39 additions & 5 deletions src/v3/xml/xmlElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,55 @@ export class XmlElement extends XmlNode {
return this
}

/**
* Creates a new element with the given name and attributes and then invokes the callback to configure it.
* @param name Element name
* @param attributes Map of element attributes
* @param arg Callback to configure the new element
*/
public appendElement(
name: string,
attributes: XmlAttributes,
cb?: Callback
arg?: Callback
): XmlElement
/**
* Creates a new element with the given name and attributes and then sets it's text content (can be a matcher)
* @param name Element name
* @param attributes Map of element attributes
* @param arg Text content to create the new element with (can be a matcher)
*/
public appendElement(
name: string,
attributes: XmlAttributes,
arg?: string
): XmlElement
public appendElement(
name: string,
attributes: XmlAttributes,
arg?: any
): XmlElement {
const el = new XmlElement(name).setAttributes(attributes)
this.executeCallback(el, cb)
if (arg) {
if (typeof arg != "function") {
el.appendText(arg)
} else {
this.executeCallback(el, arg)
}
}
this.children.push(el)

return this
}

public appendText(content: string): XmlElement {
this.children.push(new XmlText(content))

public appendText(content: string): XmlElement
public appendText(content: any): XmlElement {
if (typeof context == "string") {
this.children.push(new XmlText(content))
} else if (content["pact:matcher:type"]) {
this.children.push(new XmlText(content["value"], content))
} else {
this.children.push(new XmlText(content.toString()))
}
return this
}

Expand Down
2 changes: 1 addition & 1 deletion src/v3/xml/xmlText.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { XmlNode } from "./xmlNode"

export class XmlText extends XmlNode {
constructor(private content: string) {
constructor(private content: string, private matcher?: any) {
super()
}
}

0 comments on commit b3b5e62

Please sign in to comment.