diff --git a/rust/pact_ffi/src/mock_server/handles.rs b/rust/pact_ffi/src/mock_server/handles.rs index 9b788540f..00df26bdf 100644 --- a/rust/pact_ffi/src/mock_server/handles.rs +++ b/rust/pact_ffi/src/mock_server/handles.rs @@ -2171,6 +2171,10 @@ ffi_fn!{ /// Note that a `value` that deserialize to a JSON null will result in a /// comment being added, with the value being the JSON null. /// + /// Note that the `text` key is special and is used by + /// [`pactffi_add_text_comment`] to append comments to the array of comments. + /// Overwriting the `"text"` key is allowed, but should be used with caution. + /// /// # Safety /// /// The comments parameter must be a valid pointer to a NULL terminated UTF-8, @@ -2227,6 +2231,66 @@ ffi_fn!{ } } +ffi_fn!{ + /// Add a text comment to the interaction. + /// + /// * `interaction` - Interaction handle to set the comments for. + /// * `comment` - Comment value. + /// + /// This function will return `true` if the comments were successfully + /// updated. The `comment` must be a valid UTF-8 null-terminated string. + /// + /// Unlike [`pactffi_set_comment`], this function will always append the + /// comment to the array of comments under the `"text"` key. + /// + /// If, for any reason, the `"text"` key is not present or the associated + /// value not an array, it will be created as/replaced by an array and the + /// comment will be appended to it. + /// + /// # Safety + /// + /// The comments parameter must be a valid pointer to a NULL terminated UTF-8, + /// or NULL if the comment is to be cleared. + fn pactffi_add_text_comment(interaction: InteractionHandle, comment: *const c_char) -> bool { + let comment = match convert_cstr("comment", comment) { + Some(comment) => comment, + None => { + error!("add_text_comment: Comment value is not valid (NULL or non-UTF-8)"); + return Err(anyhow!("Comment value is not valid (NULL or non-UTF-8)")); + } + }; + + let ensure_append = |comments: &mut serde_json::Value| { + match comments { + serde_json::Value::Array(_) => { + comments.as_array_mut().unwrap().push(serde_json::Value::String(comment.to_string())); + }, + _ => { + *comments = serde_json::Value::Array(vec![serde_json::Value::String(comment.to_string())]); + } + } + }; + + interaction.with_interaction(&|_, _, inner| { + if let Some(reqres) = inner.as_v4_http_mut() { + ensure_append(reqres.comments.entry("text".to_string()).or_insert(serde_json::Value::Array(vec![]))); + Ok(()) + } else if let Some(message) = inner.as_v4_async_message_mut() { + ensure_append(message.comments.entry("text".to_string()).or_insert(serde_json::Value::Array(vec![]))); + Ok(()) + } else if let Some(sync_message) = inner.as_v4_sync_message_mut() { + ensure_append(sync_message.comments.entry("text".to_string()).or_insert(serde_json::Value::Array(vec![]))); + Ok(()) + } else { + error!("Interaction is an unknown type, is {}", inner.type_of()); + Err(anyhow!("Interaction is an unknown type, is {}", inner.type_of())) + } + }).unwrap_or(Err(anyhow!("Not value to unwrap"))).is_ok() + } { + false + } +} + fn convert_ptr_to_body(body: *const u8, size: size_t, content_type: Option) -> OptionalBody { if body.is_null() { OptionalBody::Null diff --git a/rust/pact_ffi/tests/tests.rs b/rust/pact_ffi/tests/tests.rs index b4e07b2d5..a4ec0751c 100644 --- a/rust/pact_ffi/tests/tests.rs +++ b/rust/pact_ffi/tests/tests.rs @@ -33,6 +33,7 @@ use pact_ffi::mock_server::handles::{ InteractionPart, PactHandle, pact_default_file_name, + pactffi_add_text_comment, pactffi_free_pact_handle, pactffi_given_with_params, pactffi_message_expects_to_receive, @@ -346,6 +347,46 @@ fn set_comment() { }); } +#[test] +fn add_text_comment() { + let consumer_name = CString::new("consumer").unwrap(); + let provider_name = CString::new("provider").unwrap(); + let pact_handle = pactffi_new_pact(consumer_name.as_ptr(), provider_name.as_ptr()); + let description = CString::new("set_comment").unwrap(); + let interaction = pactffi_new_interaction(pact_handle, description.as_ptr()); + + let values = vec![ + CString::new("foo").unwrap(), + CString::new("bar").unwrap(), + CString::new("hello").unwrap(), + CString::new("world").unwrap(), + ]; + + assert!(pactffi_add_text_comment(interaction, values[0].as_ptr())); + interaction.with_interaction(&|_, _, i| { + let interaction = i.as_v4_http().unwrap(); + assert_eq!(interaction.comments["text"], json!(["foo"])); + }); + + assert!(pactffi_add_text_comment(interaction, values[1].as_ptr())); + interaction.with_interaction(&|_, _, i| { + let interaction = i.as_v4_http().unwrap(); + assert_eq!(interaction.comments["text"], json!(["foo", "bar"])); + }); + + assert!(pactffi_add_text_comment(interaction, values[2].as_ptr())); + interaction.with_interaction(&|_, _, i| { + let interaction = i.as_v4_http().unwrap(); + assert_eq!(interaction.comments["text"], json!(["foo", "bar", "hello"])); + }); + + assert!(pactffi_add_text_comment(interaction, values[3].as_ptr())); + interaction.with_interaction(&|_, _, i| { + let interaction = i.as_v4_http().unwrap(); + assert_eq!(interaction.comments["text"], json!(["foo", "bar", "hello", "world"])); + }); +} + #[test_log::test] #[allow(deprecated)] fn http_consumer_feature_test() {