Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add generate_contents methods #443

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions rust/pact_ffi/src/models/async_message.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
//! V4 ASynchronous messages

use std::collections::HashMap;

use anyhow::anyhow;
use bytes::Bytes;
use futures::executor::block_on;
use libc::{c_char, c_int, c_uchar, c_uint, EXIT_FAILURE, EXIT_SUCCESS, size_t};
use pact_matching::generators::apply_generators_to_async_message;
use pact_models::bodies::OptionalBody;
use pact_models::content_types::{ContentType, ContentTypeHint};
use pact_models::generators::GeneratorTestMode;
use pact_models::provider_states::ProviderState;
use pact_models::v4::async_message::AsynchronousMessage;
use pact_models::v4::message_parts::MessageContents;
Expand Down Expand Up @@ -59,6 +64,41 @@ ffi_fn! {
}
}

ffi_fn! {
/// Generate the message contents of an `AsynchronousMessage` as a
/// `MessageContents` pointer.
///
/// This function differs from [`pactffi_async_message_get_contents`] in
/// that it will process the message contents for any generators or matchers
/// that are present in the message in order to generate the actual message
/// contents as would be received by the consumer.
///
/// # Safety
///
/// The data pointed to by the pointer must be deleted with
/// [`pactffi_message_contents_delete`][crate::models::contents::pactffi_message_contents_delete]
///
/// # Error Handling
///
/// If the message is NULL, returns NULL.
fn pactffi_async_message_generate_contents(message: *const AsynchronousMessage) -> *const MessageContents {
let message = as_ref!(message);
let context = HashMap::new();
let plugin_data = Vec::new();
let interaction_data = HashMap::new();
let contents = block_on(apply_generators_to_async_message(
&message,
&GeneratorTestMode::Consumer,
&context,
&plugin_data,
&interaction_data,
));
ptr::raw_to(contents) as *const MessageContents
} {
std::ptr::null()
}
}

ffi_fn! {
/// Get the message contents of an `AsynchronousMessage` in string form.
///
Expand Down Expand Up @@ -321,12 +361,16 @@ mod tests {
use expectest::prelude::*;
use libc::c_char;

use crate::models::async_message::{
use pact_models::generators;
use pact_models::generators::Generator;

use super::{
pactffi_async_message_delete,
pactffi_async_message_generate_contents,
pactffi_async_message_get_contents_length,
pactffi_async_message_get_contents_str,
pactffi_async_message_new,
pactffi_async_message_set_contents_str
pactffi_async_message_set_contents_str,
};

#[test]
Expand All @@ -344,4 +388,25 @@ mod tests {
expect!(str.to_str().unwrap()).to(be_equal_to("This is a string"));
expect!(len).to(be_equal_to(16));
}

#[test]
fn test_generate_contents() {
let message = pactffi_async_message_new();
let message_contents = CString::new(r#"{ "id": 1 }"#).unwrap();
let content_type = CString::new("application/json").unwrap();
pactffi_async_message_set_contents_str(message, message_contents.as_ptr(), content_type.as_ptr());

unsafe { &mut *message }.contents.generators.add_generators(generators!{
"body" => {
"$.id" => Generator::RandomInt(1000, 1000)
}
});

let contents = pactffi_async_message_generate_contents(message);

assert_eq!(
r#"{"id":1000}"#,
unsafe { &*contents }.contents.value_as_string().unwrap()
);
}
}
16 changes: 16 additions & 0 deletions rust/pact_ffi/src/models/contents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ use crate::models::message::MessageMetadataIterator;
use crate::string::optional_str;
use crate::util::*;

ffi_fn! {
/// Delete the message contents instance.
///
/// # Safety
///
/// This should only be called on a message contents that require deletion.
/// The function creating the message contents should document whether it
/// requires deletion.
///
/// Deleting a message content which is associated with an interaction
/// will result in undefined behaviour.
fn pactffi_message_contents_delete(contents: *const MessageContents) {
ptr::drop_raw(contents as *mut MessageContents);
}
}

ffi_fn! {
/// Get the message contents in string form.
///
Expand Down
115 changes: 110 additions & 5 deletions rust/pact_ffi/src/models/sync_message.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
//! V4 Synchronous request/response messages

use std::collections::HashMap;

use anyhow::anyhow;
use bytes::Bytes;
use futures::executor::block_on;
use libc::{c_char, c_int, c_uchar, c_uint, EXIT_FAILURE, EXIT_SUCCESS, size_t};
use pact_matching::generators::apply_generators_to_sync_message;
use pact_models::bodies::OptionalBody;
use pact_models::content_types::{ContentType, ContentTypeHint};
use pact_models::generators::GeneratorTestMode;
use pact_models::provider_states::ProviderState;
use pact_models::v4::message_parts::MessageContents;
use pact_models::v4::sync_message::SynchronousMessage;
Expand Down Expand Up @@ -209,6 +214,41 @@ ffi_fn! {
}
}

ffi_fn! {
/// Generate the request contents of a `SynchronousMessage` as a
/// `MessageContents` pointer.
///
/// This function differs from [`pactffi_sync_message_get_request_contents`]
/// in that it will process the message contents for any generators or
/// matchers that are present in the message in order to generate the actual
/// message contents as would be received by the consumer.
///
/// # Safety
///
/// The data pointed to by the pointer must be deleted with
/// [`pactffi_message_contents_delete`][crate::models::contents::pactffi_message_contents_delete]
///
/// # Error Handling
///
/// If the message is NULL, returns NULL.
fn pactffi_sync_message_generate_request_contents(message: *const SynchronousMessage) -> *const MessageContents {
let message = as_ref!(message);
let context = HashMap::new();
let plugin_data = Vec::new();
let interaction_data = HashMap::new();
let (contents, _) = block_on(apply_generators_to_sync_message(
&message,
&GeneratorTestMode::Consumer,
&context,
&plugin_data,
&interaction_data,
));
ptr::raw_to(contents) as *const MessageContents
} {
std::ptr::null()
}
}

ffi_fn! {
/// Get the number of response messages in the `SynchronousMessage`.
///
Expand Down Expand Up @@ -437,6 +477,46 @@ ffi_fn! {
}
}

ffi_fn! {
/// Generate the response contents of a `SynchronousMessage` as a
/// `MessageContents` pointer.
///
/// This function differs from
/// [`pactffi_sync_message_get_response_contents`] in that it will process
/// the message contents for any generators or matchers that are present in
/// the message in order to generate the actual message contents as would be
/// received by the consumer.
///
/// # Safety
///
/// The data pointed to by the pointer must be deleted with
/// [`pactffi_message_contents_delete`][crate::models::contents::pactffi_message_contents_delete]
///
/// # Error Handling
///
/// If the message is NULL, returns NULL.
fn pactffi_sync_message_generate_response_contents(message: *const SynchronousMessage, index: size_t) -> *const MessageContents {
let message = as_ref!(message);
if index >= message.response.len() {
return Ok(std::ptr::null());
}

let context = HashMap::new();
let plugin_data = Vec::new();
let interaction_data = HashMap::new();
let (_, mut responses) = block_on(apply_generators_to_sync_message(
&message,
&GeneratorTestMode::Consumer,
&context,
&plugin_data,
&interaction_data,
));
ptr::raw_to(responses.swap_remove(index)) as *const MessageContents
} {
std::ptr::null()
}
}

ffi_fn! {
/// Get a copy of the description.
///
Expand Down Expand Up @@ -551,18 +631,22 @@ mod tests {
use expectest::prelude::*;
use libc::c_char;

use crate::models::sync_message::{
use pact_models::generators;
use pact_models::generators::Generator;

use super::{
pactffi_sync_message_delete,
pactffi_sync_message_get_request_contents_str,
pactffi_sync_message_generate_request_contents,
pactffi_sync_message_get_request_contents_length,
pactffi_sync_message_get_response_contents_str,
pactffi_sync_message_get_request_contents_str,
pactffi_sync_message_get_response_contents_length,
pactffi_sync_message_get_response_contents_str,
pactffi_sync_message_new,
pactffi_sync_message_set_request_contents_str,
pactffi_sync_message_set_response_contents_str
pactffi_sync_message_set_response_contents_str,
};

#[test]
#[test]
fn get_and_set_message_contents() {
let message = pactffi_sync_message_new();
let message_contents = CString::new("This is a string").unwrap();
Expand Down Expand Up @@ -596,4 +680,25 @@ mod tests {
expect!(response_str2.to_str().unwrap()).to(be_equal_to("This is another string"));
expect!(response_len2).to(be_equal_to(22));
}

#[test]
fn test_generate_contents() {
let message = pactffi_sync_message_new();
let message_contents = CString::new(r#"{ "id": 1 }"#).unwrap();
let content_type = CString::new("application/json").unwrap();
pactffi_sync_message_set_request_contents_str(message, message_contents.as_ptr(), content_type.as_ptr());

unsafe { &mut *message }.request.generators.add_generators(generators!{
"body" => {
"$.id" => Generator::RandomInt(1000, 1000)
}
});

let contents = pactffi_sync_message_generate_request_contents(message);

assert_eq!(
r#"{"id":1000}"#,
unsafe { &*contents }.contents.value_as_string().unwrap()
);
}
}
Loading