Skip to content

Commit

Permalink
feat: Support plugins generating interaction content
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Dec 9, 2022
1 parent 2cf5d8a commit 1744ddc
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 92 deletions.
20 changes: 11 additions & 9 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions rust/pact_matching/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pact_matching"
version = "0.12.16"
version = "1.0.0"
authors = ["Ronald Holshausen <ronald.holshausen@gmail.com>"]
edition = "2021"
description = "Pact-Rust support library that implements request and response matching logic"
Expand All @@ -15,7 +15,7 @@ exclude = [
]

[dependencies]
pact_models = "1.0.1"
pact_models = "1.0.2"
anyhow = "1.0.57"
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
Expand All @@ -40,10 +40,10 @@ http = "0.2.7"
mime = "0.3.16"
bytes = { version = "1.1.0", features = ["serde"] }
tokio = { version = "1.18.2", features = ["full"] }
pact-plugin-driver = "^0.1"
pact-plugin-driver = "0.2"
md5 = "0.7.0"
tracing = "0.1.36" # This needs to be the same version across all the libs (i.e. plugin driver, pact ffi)
tracing-core = "0.1.29" # This needs to be the same version across all the pact libs (i.e. plugin driver, pact ffi)
tracing = "0.1.37" # This needs to be the same version across all the libs (i.e. plugin driver, pact ffi)
tracing-core = "0.1.30" # This needs to be the same version across all the pact libs (i.e. plugin driver, pact ffi)

[dependencies.reqwest]
version = "0.11.10"
Expand Down
26 changes: 0 additions & 26 deletions rust/pact_matching/src/generator_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use expectest::prelude::*;
use serde_json::Value;

use pact_models::bodies::OptionalBody;
use pact_models::content_types::{JSON, TEXT};
use pact_models::generators;
use pact_models::generators::{ContentTypeHandler, Generator, JsonHandler};
use pact_models::path_exp::DocPath;
Expand Down Expand Up @@ -86,31 +85,6 @@ async fn applies_query_generator_for_query_parameters_to_the_copy_of_the_request
expect!(query_val).to_not(be_equal_to("a"));
}

#[tokio::test]
async fn apply_generator_to_empty_body_test() {
expect!(generators_process_body(&GeneratorTestMode::Provider, &OptionalBody::Empty,
Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}).await.unwrap()).to(be_equal_to(OptionalBody::Empty));
expect!(generators_process_body(&GeneratorTestMode::Provider, &OptionalBody::Null,
Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}).await.unwrap()).to(be_equal_to(OptionalBody::Null));
expect!(generators_process_body(&GeneratorTestMode::Provider, &OptionalBody::Missing,
Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}).await.unwrap()).to(be_equal_to(OptionalBody::Missing));
}

#[tokio::test]
async fn do_not_apply_generators_if_there_are_no_body_generators() {
let body = OptionalBody::Present("{\"a\":100,\"b\":\"B\"}".into(), Some(JSON.clone()), None);
expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(JSON.clone()),
&hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}).await.unwrap()).to(
be_equal_to(body));
}

#[tokio::test]
async fn apply_generator_to_text_body_test() {
let body = OptionalBody::Present("some text".into(), None, None);
expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(TEXT.clone()),
&hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}).await.unwrap()).to(be_equal_to(body));
}

#[tokio::test]
async fn applies_body_generator_to_the_copy_of_the_request() {
let request = HttpRequest { body: OptionalBody::Present("{\"a\": 100, \"b\": \"B\"}".into(), None, None),
Expand Down
114 changes: 114 additions & 0 deletions rust/pact_matching/src/generators/bodies.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! Functions to apply generators to body contents

use std::collections::HashMap;

use pact_plugin_driver::catalogue_manager::find_content_generator;
use serde_json::Value;
use tracing::{debug, error, warn};

use pact_models::bodies::OptionalBody;
use pact_models::content_types::ContentType;
use pact_models::generators::{ContentTypeHandler, Generator, GeneratorTestMode, JsonHandler, VariantMatcher};
use pact_models::path_exp::DocPath;
use pact_models::plugins::PluginData;
use pact_models::xml_utils::parse_bytes;

use crate::generators::XmlHandler;

/// Apply the generators to the body, returning a new body
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),
plugin_data: &Vec<PluginData>,
interaction_data: &HashMap<String, HashMap<String, Value>>
) -> anyhow::Result<OptionalBody> {
match content_type {
Some(content_type) => if content_type.is_json() {
debug!("apply_body_generators: JSON content type");
let result: Result<Value, serde_json::Error> = serde_json::from_slice(&body.value().unwrap_or_default());
match result {
Ok(val) => {
let mut handler = JsonHandler { value: val };
Ok(handler.process_body(generators, mode, context, &matcher.boxed()).unwrap_or_else(|err| {
error!("Failed to generate the body: {}", err);
body.clone()
}))
},
Err(err) => {
error!("Failed to parse the body, so not applying any generators: {}", err);
Ok(body.clone())
}
}
} else if content_type.is_xml() {
debug!("apply_body_generators: XML content type");
match parse_bytes(&body.value().unwrap_or_default()) {
Ok(val) => {
let mut handler = XmlHandler { value: val.as_document() };
Ok(handler.process_body(generators, mode, context, &matcher.boxed()).unwrap_or_else(|err| {
error!("Failed to generate the body: {}", err);
body.clone()
}))
},
Err(err) => {
error!("Failed to parse the body, so not applying any generators: {}", err);
Ok(body.clone())
}
}
} else if let Some(content_generator) = find_content_generator(&content_type) {
debug!("apply_body_generators: Found a content generator from a plugin");
let generators = generators.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect();
content_generator.generate_content(&content_type, &generators, body, plugin_data, interaction_data, context).await
} else {
warn!("Unsupported content type {} - Generators only support JSON and XML", content_type);
Ok(body.clone())
},
_ => Ok(body.clone())
}
}

#[cfg(test)]
mod tests {
use expectest::prelude::*;
use maplit::hashmap;

use pact_models::bodies::OptionalBody;
use pact_models::content_types::{JSON, TEXT};
use pact_models::generators::GeneratorTestMode;

use super::generators_process_body;
use crate::DefaultVariantMatcher;

#[tokio::test]
async fn apply_generator_to_empty_body_test() {
expect!(generators_process_body(&GeneratorTestMode::Provider, &OptionalBody::Empty,
Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}, &vec![], &hashmap!{})
.await.unwrap()).to(be_equal_to(OptionalBody::Empty));
expect!(generators_process_body(&GeneratorTestMode::Provider, &OptionalBody::Null,
Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}, &vec![], &hashmap!{})
.await.unwrap()).to(be_equal_to(OptionalBody::Null));
expect!(generators_process_body(&GeneratorTestMode::Provider, &OptionalBody::Missing,
Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}, &vec![], &hashmap!{})
.await.unwrap()).to(be_equal_to(OptionalBody::Missing));
}

#[tokio::test]
async fn do_not_apply_generators_if_there_are_no_body_generators() {
let body = OptionalBody::Present("{\"a\":100,\"b\":\"B\"}".into(), Some(JSON.clone()), None);
expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(JSON.clone()),
&hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to(
be_equal_to(body));
}

#[tokio::test]
async fn apply_generator_to_text_body_test() {
let body = OptionalBody::Present("some text".into(), None, None);
expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(TEXT.clone()),
&hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to(be_equal_to(body));
}
}
Loading

0 comments on commit 1744ddc

Please sign in to comment.