From 598377d489aaf45c3c56c977cc817cf70b0fd0d9 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 14 Jun 2024 20:11:49 +1000 Subject: [PATCH] feat(ffi): add async message iterator Add a new V4 asynchronous message iterator. This mirrors the existing `pact_handle_get_sync_message_iter` and `pact_handle_get_http_iter`, and is the V4 counterpart for `pact_handle_get_message_iter`. I have not marked the `pact_handle_get_message_iter` as deprecated; however, if the functionality of the new iterator supplants the older `PactMessageIterator`, it may be worth documenting the deprecation and directing people to use the new function/iterator. Signed-off-by: JP-Ellis --- rust/pact_ffi/src/mock_server/handles.rs | 28 +++++++++- rust/pact_ffi/src/models/iterators.rs | 69 ++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/rust/pact_ffi/src/mock_server/handles.rs b/rust/pact_ffi/src/mock_server/handles.rs index 5a25b6d4f..978ecfd5a 100644 --- a/rust/pact_ffi/src/mock_server/handles.rs +++ b/rust/pact_ffi/src/mock_server/handles.rs @@ -160,7 +160,7 @@ use crate::mock_server::bodies::{ get_content_type_hint, part_body_replace_marker }; -use crate::models::iterators::{PactMessageIterator, PactSyncHttpIterator, PactSyncMessageIterator}; +use crate::models::iterators::{PactAsyncMessageIterator, PactMessageIterator, PactSyncHttpIterator, PactSyncMessageIterator}; use crate::ptr; #[derive(Debug, Clone)] @@ -2487,6 +2487,32 @@ ffi_fn! { } } +ffi_fn! { + /// Get an iterator over all the asynchronous messages of the Pact. + /// The returned iterator needs to be freed with `pactffi_pact_async_message_iter_delete`. + /// + /// # Safety + /// + /// The iterator contains a copy of the Pact, so it is always safe to use. + /// + /// # Error Handling + /// + /// On failure, this function will return a NULL pointer. + /// + /// This function may fail if any of the Rust strings contain embedded + /// null ('\0') bytes. + fn pactffi_pact_handle_get_async_message_iter(pact: PactHandle) -> *mut PactAsyncMessageIterator { + let v4_pact = pact.with_pact(&|_, inner| { + // Ok to unwrap this, as any non-v4 pact will be upgraded + inner.pact.as_v4_pact().unwrap() + }).ok_or_else(|| anyhow!("Pact handle is not valid"))?; + let iter = PactAsyncMessageIterator::new(v4_pact); + ptr::raw_to(iter) + } { + std::ptr::null_mut() + } +} + ffi_fn! { /// Get an iterator over all the synchronous request/response messages of the Pact. /// The returned iterator needs to be freed with `pactffi_pact_sync_message_iter_delete`. diff --git a/rust/pact_ffi/src/models/iterators.rs b/rust/pact_ffi/src/models/iterators.rs index 65bdac427..74bbff115 100644 --- a/rust/pact_ffi/src/models/iterators.rs +++ b/rust/pact_ffi/src/models/iterators.rs @@ -7,6 +7,7 @@ use tracing::trace; use pact_models::message::Message; use pact_models::message_pact::MessagePact; use pact_models::v4::pact::V4Pact; +use pact_models::v4::async_message::AsynchronousMessage; use pact_models::v4::sync_message::SynchronousMessage; use pact_models::v4::synch_http::SynchronousHttp; use pact_models::v4::V4InteractionType; @@ -77,6 +78,74 @@ ffi_fn! { } } +/// An iterator over asynchronous messages in a V4 pact. +#[derive(Debug)] +#[allow(missing_copy_implementations)] +pub struct PactAsyncMessageIterator { + current: usize, + messages: Vec +} + +impl PactAsyncMessageIterator { + /// Create a new iterator over all asynchronous messages in the pact + pub fn new(pact: V4Pact) -> Self { + PactAsyncMessageIterator { + current: 0, + messages: pact.filter_interactions(V4InteractionType::Asynchronous_Messages) + .iter() + .map(|i| i.as_v4_async_message().unwrap()) + .collect() + } + } + + /// Get the next message in the pact. + fn next(&mut self) -> Option<&mut AsynchronousMessage> { + let idx = self.current; + self.current += 1; + self.messages.get_mut(idx) + } +} + +ffi_fn! { + /// Get the next asynchronous from the V4 pact. As the messages returned are + /// owned by the iterator, they do not need to be deleted but will be + /// cleaned up when the iterator is deleted. + /// + /// Will return a NULL pointer when the iterator has advanced past the end + /// of the list. + /// + /// # Safety + /// + /// This function is safe. + /// + /// Deleting a message returned by the iterator can lead to undefined + /// behaviour. + /// + /// # Error Handling + /// + /// This function will return a NULL pointer if passed a NULL pointer or if + /// an error occurs. + fn pactffi_pact_async_message_iter_next(iter: *mut PactAsyncMessageIterator) -> *mut AsynchronousMessage { + let iter = as_mut!(iter); + match iter.next() { + Some(message) => message as *mut AsynchronousMessage, + None => { + trace!("iter past the end of messages"); + std::ptr::null_mut() + } + } + } { + std::ptr::null_mut() + } +} + +ffi_fn! { + /// Free the iterator when you're done using it. + fn pactffi_pact_async_message_iter_delete(iter: *mut PactAsyncMessageIterator) { + ptr::drop_raw(iter); + } +} + /// An iterator over synchronous request/response messages in a V4 pact. #[derive(Debug)] #[allow(missing_copy_implementations)]