use std::collections::HashMap;
use maplit::hashmap;
use pact_models::bodies::OptionalBody;
use pact_models::content_types::ContentType;
use pact_models::generators::{
apply_generators,
ContentTypeHandler,
GenerateValue,
Generator,
GeneratorCategory,
GeneratorTestMode,
NoopVariantMatcher,
VariantMatcher
};
use pact_models::matchingrules::MatchingRuleCategory;
use pact_models::path_exp::DocPath;
use serde_json::{self, Value};
use sxd_document::dom::Document;
use tracing::{debug, error};
use pact_models::http_parts::HttpPart;
use pact_models::plugins::PluginData;
use pact_models::v4::async_message::AsynchronousMessage;
use pact_models::v4::message_parts::MessageContents;
use pact_models::v4::sync_message::SynchronousMessage;
use crate::{CoreMatchingContext, DiffConfig, MatchingContext};
use crate::json::compare_json;
pub mod bodies;
pub struct XmlHandler<'a> {
pub value: Document<'a>
}
impl <'a> ContentTypeHandler<Document<'a>> for XmlHandler<'a> {
fn process_body(
&mut self,
_generators: &HashMap<DocPath, Generator>,
_mode: &GeneratorTestMode,
_context: &HashMap<&str, Value>,
_matcher: &Box<dyn VariantMatcher + Send + Sync>
) -> Result<OptionalBody, String> {
error!("UNIMPLEMENTED: Generators are not currently supported with XML");
Err("Generators are not supported with XML".to_string())
}
fn apply_key(
&mut self,
_key: &DocPath,
_generator: &dyn GenerateValue<Document<'a>>,
_context: &HashMap<&str, Value>,
_matcher: &Box<dyn VariantMatcher + Send + Sync>
) {
error!("UNIMPLEMENTED: Generators are not currently supported with XML");
}
}
#[deprecated(note = "moved to the generators::bodies module", since = "0.12.16")]
pub async fn generators_process_body(
mode: &GeneratorTestMode,
body: &OptionalBody,
content_type: Option<ContentType>,
context: &HashMap<&str, Value>,
generators: &HashMap<DocPath, Generator>,
matcher: &(dyn VariantMatcher + Send + Sync)
) -> anyhow::Result<OptionalBody> {
bodies::generators_process_body(mode, body, content_type, context, generators, matcher, &vec![], &hashmap!{}).await
}
pub(crate) fn find_matching_variant<T>(
value: &T,
variants: &[(usize, MatchingRuleCategory, HashMap<DocPath, Generator>)],
callback: &dyn Fn(&DocPath, &T, &dyn MatchingContext) -> bool
) -> Option<(usize, HashMap<DocPath, Generator>)>
where T: Clone + std::fmt::Debug {
let result = variants.iter()
.find(|(index, rules, _)| {
debug!("find_matching_variant: Comparing variant {} with value '{:?}'", index, value);
let context = CoreMatchingContext::new(DiffConfig::NoUnexpectedKeys,
rules, &hashmap!{});
let matches = callback(&DocPath::root(), value, &context);
debug!("find_matching_variant: Comparing variant {} => {}", index, matches);
matches
});
debug!("find_matching_variant: result = {:?}", result);
result.map(|(index, _, generators)| (*index, generators.clone()))
}
#[derive(Debug, Clone)]
pub struct DefaultVariantMatcher;
impl VariantMatcher for DefaultVariantMatcher {
fn find_matching_variant(
&self,
value: &Value,
variants: &Vec<(usize, MatchingRuleCategory, HashMap<DocPath, Generator>)>
) -> Option<(usize, HashMap<DocPath, Generator>)> {
let callback = |path: &DocPath, value: &Value, context: &dyn MatchingContext| {
compare_json(path, value, value, context).is_ok()
};
find_matching_variant(value, variants, &callback)
}
fn boxed(&self) -> Box<dyn VariantMatcher + Send + Sync> {
Box::new(self.clone())
}
}
pub async fn apply_generators_to_sync_message(
message: &SynchronousMessage,
mode: &GeneratorTestMode,
context: &HashMap<&str, Value>,
plugin_data: &Vec<PluginData>,
interaction_data: &HashMap<String, HashMap<String, Value>>
) -> (MessageContents, Vec<MessageContents>) {
let mut request = message.request.clone();
let variant_matcher = NoopVariantMatcher {};
let vm_boxed = variant_matcher.boxed();
let generators = request.build_generators(&GeneratorCategory::METADATA);
if !generators.is_empty() {
debug!("Applying request metadata generators...");
apply_generators(mode, &generators, &mut |key, generator| {
if let Some(k) = key.first_field() {
let value = request.metadata.get(k).cloned().unwrap_or_default();
if let Ok(v) = generator.generate_value(&value, context, &vm_boxed) {
request.metadata.insert(k.to_string(), v);
}
}
});
}
let generators = request.build_generators(&GeneratorCategory::BODY);
if !generators.is_empty() && request.contents.is_present() {
debug!("Applying request content generators...");
match bodies::generators_process_body(mode, &request.contents, request.content_type(),
context, &generators, &variant_matcher, plugin_data, interaction_data).await {
Ok(contents) => request.contents = contents,
Err(err) => error!("Failed to generate the message contents, will use the original: {}", err)
}
}
let mut responses = message.response.clone();
for response in responses.iter_mut() {
let generators = response.build_generators(&GeneratorCategory::METADATA);
if !generators.is_empty() {
debug!("Applying response metadata generators...");
apply_generators(mode, &generators, &mut |key, generator| {
if let Some(k) = key.first_field() {
let value = response.metadata.get(k).cloned().unwrap_or_default();
if let Ok(v) = generator.generate_value(&value, context, &vm_boxed) {
response.metadata.insert(k.to_string(), v);
}
}
});
}
let generators = response.build_generators(&GeneratorCategory::BODY);
if !generators.is_empty() && response.contents.is_present() {
debug!("Applying response content generators...");
match bodies::generators_process_body(mode, &response.contents, response.content_type(),
context, &generators, &variant_matcher, plugin_data, interaction_data).await {
Ok(contents) => response.contents = contents,
Err(err) => error!("Failed to generate the message contents, will use the original: {}", err)
}
}
}
(request, responses)
}
pub async fn apply_generators_to_async_message(
message: &AsynchronousMessage,
mode: &GeneratorTestMode,
context: &HashMap<&str, Value>,
plugin_data: &Vec<PluginData>,
interaction_data: &HashMap<String, HashMap<String, Value>>
) -> MessageContents {
let mut copy = message.contents.clone();
let variant_matcher = NoopVariantMatcher {};
let vm_boxed = variant_matcher.boxed();
let generators = message.build_generators(&GeneratorCategory::METADATA);
if !generators.is_empty() {
debug!("Applying metadata generators...");
apply_generators(mode, &generators, &mut |key, generator| {
if let Some(k) = key.first_field() {
let value = message.contents.metadata.get(k).cloned().unwrap_or_default();
if let Ok(v) = generator.generate_value(&value, context, &vm_boxed) {
copy.metadata.insert(k.to_string(), v);
}
}
});
}
let generators = message.build_generators(&GeneratorCategory::BODY);
if !generators.is_empty() && message.contents.contents.is_present() {
debug!("Applying content generators...");
match bodies::generators_process_body(mode, &message.contents.contents, message.contents.content_type(),
context, &generators, &variant_matcher, plugin_data, interaction_data).await {
Ok(contents) => copy.contents = contents,
Err(err) => error!("Failed to generate the message contents, will use the original: {}", err)
}
}
copy
}
#[cfg(test)]
mod tests {
use expectest::prelude::*;
use maplit::hashmap;
use pact_models::generators::{GenerateValue, Generator, VariantMatcher};
use pact_models::matchingrules::MatchingRule;
use pact_models::matchingrules_list;
use pact_models::path_exp::DocPath;
use pretty_assertions::assert_eq;
use serde_json::json;
use crate::generators::DefaultVariantMatcher;
#[test_log::test]
fn array_contains_generator_test() {
let generator = Generator::ArrayContains(vec![
(0, matchingrules_list! {
"body"; "$.href" => [ MatchingRule::Regex(".*(\\/orders\\/\\d+)$".into()) ]
}, hashmap! {
DocPath::new_unwrap("$.href") =>
Generator::MockServerURL(
"http://localhost:8080/orders/1234".into(),
".*(\\/orders\\/\\d+)$".into(),
)
}),
(1, matchingrules_list! {
"body"; "$.href" => [ MatchingRule::Regex(".*(\\/orders\\/\\d+)$".into()) ]
}, hashmap! {
DocPath::new_unwrap("$.href") =>
Generator::MockServerURL(
"http://localhost:8080/orders/1234".into(),
".*(\\/orders\\/\\d+)$".into(),
)
})
]);
let value = json!([
{
"href": "http://localhost:9000/orders/1234",
"method": "PUT",
"name": "update"
},
{
"href": "http://localhost:9000/orders/1234",
"method": "DELETE",
"name": "delete"
}
]);
let context = hashmap! {
"mockServer" => json!({
"href": "https://somewhere.else:1234/subpath"
})
};
let generated = generator.generate_value(&value, &context, &DefaultVariantMatcher.boxed());
expect!(generated.as_ref()).to(be_ok());
let generated_value = generated.unwrap();
assert_eq!(generated_value, json!([
{
"href": "https://somewhere.else:1234/subpath/orders/1234",
"method": "PUT",
"name": "update"
},
{
"href": "https://somewhere.else:1234/subpath/orders/1234",
"method": "DELETE",
"name": "delete"
}
]));
}
}