diff --git a/rust/pact_matching/Cargo.toml b/rust/pact_matching/Cargo.toml index ee5b4654f..b500095f3 100644 --- a/rust/pact_matching/Cargo.toml +++ b/rust/pact_matching/Cargo.toml @@ -15,11 +15,12 @@ exclude = [ ] [features] -default = ["datetime", "xml", "plugins", "multipart"] +default = ["datetime", "xml", "plugins", "multipart", "form_urlencoded"] datetime = ["pact_models/datetime", "pact-plugin-driver?/datetime", "dep:chrono"] # Support for date/time matchers and expressions xml = ["pact_models/xml", "pact-plugin-driver?/xml", "dep:sxd-document"] # support for matching XML documents plugins = ["dep:pact-plugin-driver"] multipart = ["dep:multer"] # suport for MIME multipart bodies +form_urlencoded = ["pact_models/form_urlencoded"] # suport for matching form urlencoded [dependencies] ansi_term = "0.12.1" @@ -41,7 +42,7 @@ mime = "0.3.17" multer = { version = "3.0.0", features = ["all"], optional = true } nom = "7.1.3" onig = { version = "6.4.0", default-features = false } -pact_models = { version = "~1.2.5", default-features = false } +pact_models = { version = "~1.2.6", default-features = false } pact-plugin-driver = { version = "~0.7.2", optional = true, default-features = false } rand = "0.8.5" reqwest = { version = "0.12.3", default-features = false, features = ["rustls-tls-native-roots", "json"] } diff --git a/rust/pact_matching/src/generators/bodies.rs b/rust/pact_matching/src/generators/bodies.rs index 48c3cf19b..a6c6344cf 100644 --- a/rust/pact_matching/src/generators/bodies.rs +++ b/rust/pact_matching/src/generators/bodies.rs @@ -15,6 +15,8 @@ use pact_models::plugins::PluginData; #[cfg(feature = "xml")] use crate::generators::XmlHandler; +#[cfg(feature = "form_urlencoded")] use pact_models::generators::form_urlencoded::FormUrlEncodedHandler; + /// Apply the generators to the body, returning a new body #[allow(unused_variables)] pub async fn generators_process_body( @@ -67,6 +69,30 @@ pub async fn generators_process_body( warn!("Generating XML documents requires the xml feature to be enabled"); Ok(body.clone()) } + } else if content_type.is_form_urlencoded() { + debug!("apply_body_generators: FORM URLENCODED content type"); + #[cfg(feature = "form_urlencoded")] + { + let result: Result, serde_urlencoded::de::Error> = serde_urlencoded::from_bytes(&body.value().unwrap_or_default()); + match result { + Ok(val) => { + let mut handler = FormUrlEncodedHandler { params: 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()) + } + } + } + #[cfg(not(feature = "form_urlencoded"))] + { + warn!("Generating FORM URLENCODED query string requires the form_urlencoded feature to be enabled"); + Ok(body.clone()) + } } else { #[cfg(feature = "plugins")] @@ -97,9 +123,11 @@ mod tests { use expectest::prelude::*; use maplit::hashmap; + use pact_models::generators::Generator; use pact_models::bodies::OptionalBody; - use pact_models::content_types::{JSON, TEXT}; + use pact_models::content_types::{JSON, TEXT, XML, FORM_URLENCODED}; use pact_models::generators::GeneratorTestMode; + use pact_models::path_exp::DocPath; use super::generators_process_body; use crate::DefaultVariantMatcher; @@ -131,4 +159,25 @@ mod tests { expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to(be_equal_to(body)); } + + #[tokio::test] + async fn apply_generator_to_json_body_test() { + let body = OptionalBody::Present("{\"a\":100}".into(), None, None); + expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(JSON.clone()), + &hashmap!{}, &hashmap!{DocPath::new_unwrap("$.a") => Generator::RandomInt(0, 10)}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to_not(be_equal_to(body)); + } + + #[tokio::test] + async fn do_not_apply_generator_to_xml_body_because_unimplemented() { + let body = OptionalBody::Present("100".into(), None, None); + expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(XML.clone()), + &hashmap!{}, &hashmap!{DocPath::new_unwrap("$.name") => Generator::RandomInt(0, 10)}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to(be_equal_to(body)); + } + + #[tokio::test] + async fn apply_generator_to_form_urlencoded_body_test() { + let body = OptionalBody::Present("a=100".into(), None, None); + expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(FORM_URLENCODED.clone()), + &hashmap!{}, &hashmap!{DocPath::new_unwrap("$.a") => Generator::RandomInt(0, 10)}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to_not(be_equal_to(body)); + } }