diff --git a/rust/pact_ffi/src/mock_server/handles.rs b/rust/pact_ffi/src/mock_server/handles.rs index 2aefce0ec..056c5dcd0 100644 --- a/rust/pact_ffi/src/mock_server/handles.rs +++ b/rust/pact_ffi/src/mock_server/handles.rs @@ -2137,6 +2137,67 @@ pub extern fn pactffi_message_with_metadata(message_handle: MessageHandle, key: } } +/// Adds expected metadata to the Message +/// +/// * `key` - metadata key +/// * `value` - metadata value, supports JSON structures with matchers and generators +#[no_mangle] +pub extern fn pactffi_message_with_metadata_v2(message_handle: MessageHandle, key: *const c_char, value: *const c_char) { + if let Some(key) = convert_cstr("key", key) { + let value = convert_cstr("value", value).unwrap_or_default(); + + message_handle.with_message(&|_, inner, _| { + if let Some(message) = inner.as_v4_async_message_mut() { + let matching_rules = message.contents.matching_rules.add_category(Category::METADATA); + let generators = &mut message.contents.generators; + let value = match serde_json::from_str(value) { + Ok(json) => match json { + Value::Object(ref obj) => { + if let Some(matcher_type) = obj.get("pact:matcher:type") { + debug!("detected pact:matcher:type, will configure a matcher"); + let matcher_type = json_to_string(matcher_type); + let attributes = Value::Object(obj.clone()); + let rule = MatchingRule::create(matcher_type.as_str(), &attributes).unwrap(); + matching_rules.add_rule(DocPath::new(key).unwrap(), rule.clone(), RuleLogic::And); + } + if let Some(gen) = obj.get("pact:generator:type") { + debug!("detected pact:generator:type, will configure a generators"); + if let Some(generator) = Generator::from_map(&json_to_string(gen), obj) { + generators.add_generator_with_subcategory(&GeneratorCategory::METADATA, DocPath::new(key).unwrap(), generator); + } + } + match obj.get("value") { + Some(inner) => match inner { + Value::Array(_) => { + warn!("Array value is not supported"); + Value::Null + } + Value::Object(_) => { + warn!("Object value is not supported"); + Value::Null + } + _ => inner.clone() + }, + None => Value::Null + } + }, + Value::String(string) => Value::String(string.to_string()), + _ => { + warn!("Only support object or string"); + Value::String(value.to_string()) + } + }, + Err(err) => { + warn!("Failed to parse metadata from JSON - {}", err); + Value::String(value.to_string()) + } + }; + message.contents.metadata.insert(key.to_string(), value); + } + }); + } +} + /// Reifies the given message /// /// Reification is the process of stripping away any matchers, and returning the original contents. diff --git a/rust/pact_ffi/tests/tests.rs b/rust/pact_ffi/tests/tests.rs index e3b993eb1..cd1d23369 100644 --- a/rust/pact_ffi/tests/tests.rs +++ b/rust/pact_ffi/tests/tests.rs @@ -18,6 +18,7 @@ use reqwest::header::CONTENT_TYPE; use tempfile::TempDir; use serde_json::{json, Value}; use rstest::rstest; +use regex::Regex; #[allow(deprecated)] use pact_ffi::mock_server::{ @@ -35,6 +36,7 @@ use pact_ffi::mock_server::handles::{ pactffi_message_reify, pactffi_message_with_contents, pactffi_message_with_metadata, + pactffi_message_with_metadata_v2, pactffi_new_interaction, pactffi_new_message, pactffi_new_message_pact, @@ -409,6 +411,35 @@ fn message_xml_consumer_feature_test() { expect!(res).to(be_eq(0)); } +#[test] +fn message_consumer_with_matchers_and_generators_test() { + let consumer_name = CString::new("message-consumer").unwrap(); + let provider_name = CString::new("message-provider").unwrap(); + let description = CString::new("message_request_with_matchers_and_generators").unwrap(); + let content_type = CString::new("application/json").unwrap(); + let metadata_key = CString::new("message-queue-name").unwrap(); + let metadata_val = CString::new("{\"pact:generator:type\":\"RandomString\",\"value\":\"some text\",\"pact:matcher:type\":\"type\"}").unwrap(); + let request_body_with_matchers = CString::new("{\"id\": {\"pact:generator:type\":\"RandomInt\",\"min\":1,\"pact:matcher:type\":\"integer\"}}").unwrap(); + let file_path = CString::new("/tmp/pact").unwrap(); + let given = CString::new("a functioning FFI interface").unwrap(); + let receive_description = CString::new("a request to test the FFI interface").unwrap(); + + let message_pact_handle = pactffi_new_message_pact(consumer_name.as_ptr(), provider_name.as_ptr()); + let message_handle = pactffi_new_message(message_pact_handle.clone(), description.as_ptr()); + pactffi_message_given(message_handle.clone(), given.as_ptr()); + pactffi_message_expects_to_receive(message_handle.clone(), receive_description.as_ptr()); + let body_bytes = request_body_with_matchers.as_bytes(); + pactffi_message_with_contents(message_handle.clone(), content_type.as_ptr(), body_bytes.as_ptr(), body_bytes.len()); + pactffi_message_with_metadata_v2(message_handle.clone(), metadata_key.as_ptr(), metadata_val.as_ptr()); + let res: *const c_char = pactffi_message_reify(message_handle.clone()); + let reified = unsafe { CStr::from_ptr(res) }.to_str().unwrap(); + let message = serde_json::from_str(reified).unwrap_or(json!({})); + expect!(Regex::new("\\d+").unwrap().is_match(message.get("contents").unwrap().get("id").unwrap().to_string().as_str())).to(be_true()); + expect!(Regex::new("[\\d\\w]+").unwrap().is_match(message.get("metadata").unwrap().get("message-queue-name").unwrap().to_string().as_str())).to(be_true()); + let res = pactffi_write_message_pact_file(message_pact_handle.clone(), file_path.as_ptr(), true); + expect!(res).to(be_eq(0)); +} + #[test] fn pactffi_verifier_cli_args_test() { let data = pactffi_verifier_cli_args();