From 61b86851efb1543ced4766b0895f078f18813397 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 21 Jun 2024 13:44:01 +1000 Subject: [PATCH 01/23] chore(v3): re-export Pact and Verifier at root Use the `__all__` to mark the `Pact` and `Verifier` imports as public re-exports, thereby removing the need to silence F401. Signed-off-by: JP-Ellis --- src/pact/v3/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pact/v3/__init__.py b/src/pact/v3/__init__.py index 91329f2dc..15a207faa 100644 --- a/src/pact/v3/__init__.py +++ b/src/pact/v3/__init__.py @@ -70,8 +70,8 @@ import warnings -from pact.v3.pact import Pact # noqa: F401 -from pact.v3.verifier import Verifier # noqa: F401 +from pact.v3.pact import Pact +from pact.v3.verifier import Verifier warnings.warn( "The `pact.v3` module is not yet stable. Use at your own risk, and expect " @@ -79,3 +79,5 @@ stacklevel=2, category=ImportWarning, ) + +__all__ = ["Pact", "Verifier"] From cc3034a7e5cb4081d0dfcea262b9e3f958cce0af Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 21 Jun 2024 13:42:50 +1000 Subject: [PATCH 02/23] feat(ffi): upgrade ffi 0.4.21 Main changes include: - Addition of a `PactAsyncMessageIterator` (and related functions) - Addition of `*_generate_contents` functions which are analogous to the `*_get_contents` functions, but replace any matchers/generators with the relevant substitution in order to generate a 'real' payload. - Addition of `with_metadata` to add metadat to an interaction - Addition of `with_generators` to add generators to an interaction - Deprecation of the `MessagePactHandle` and `MessageHandle` Signed-off-by: JP-Ellis --- .github/workflows/build.yml | 5 +- hatch_build.py | 6 +- src/pact/v3/ffi.py | 798 ++++++++++++------------------- src/pact/v3/interaction/_base.py | 100 ++++ 4 files changed, 413 insertions(+), 496 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3c15ea6f..258d8ad7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,9 +135,12 @@ jobs: - os: ubuntu-20.04 archs: aarch64 build: musllinux - - os: macos-12 + - os: macos-14 archs: arm64 build: "" + - os: windows-2019 + archs: ARM64 + build: "" steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 diff --git a/hatch_build.py b/hatch_build.py index 9842fb6a4..ab5e84e0f 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -36,7 +36,7 @@ # Latest version available at: # https://github.com/pact-foundation/pact-reference/releases -PACT_LIB_VERSION = os.getenv("PACT_LIB_VERSION", "0.4.19") +PACT_LIB_VERSION = os.getenv("PACT_LIB_VERSION", "0.4.21") PACT_LIB_URL = "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{version}/{prefix}pact_ffi-{os}-{machine}.{ext}" @@ -256,7 +256,7 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912 if platform.startswith("macosx"): os = "macos" if platform.endswith("arm64"): - machine = "aarch64-apple-darwin" + machine = "aarch64" elif platform.endswith("x86_64"): machine = "x86_64" else: @@ -274,6 +274,8 @@ def _pact_lib_url(self, version: str) -> str: # noqa: C901, PLR0912 if platform.endswith("amd64"): machine = "x86_64" + elif platform.endswith(("arm64", "aarch64")): + machine = "aarch64" else: raise UnsupportedPlatformError(platform) return PACT_LIB_URL.format( diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index e23d94b86..df18845af 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -193,31 +193,68 @@ class MessageMetadataIterator: ... class MessageMetadataPair: ... -class MessagePact: ... - +class Mismatch: ... -class MessagePactHandle: ... +class Mismatches: ... -class MessagePactMessageIterator: ... +class MismatchesIterator: ... -class MessagePactMetadataIterator: ... +class Pact: ... -class MessagePactMetadataTriple: ... +class PactAsyncMessageIterator: + """ + Iterator over a Pact's asynchronous messages. + """ -class Mismatch: ... + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new Pact Asynchronous Message Iterator. + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct PactAsyncMessageIterator *": + msg = ( + "ptr must be a struct PactAsyncMessageIterator, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr -class Mismatches: ... + def __str__(self) -> str: + """ + Nice string representation. + """ + return "PactAsyncMessageIterator" + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"PactAsyncMessageIterator({self._ptr!r})" -class MismatchesIterator: ... + def __del__(self) -> None: + """ + Destructor for the Pact Synchronous Message Iterator. + """ + pact_async_message_iter_delete(self) + def __iter__(self) -> Self: + """ + Return the iterator itself. + """ + return self -class Pact: ... + def __next__(self) -> AsynchronousMessage: + """ + Get the next message from the iterator. + """ + return pact_async_message_iter_next(self) class PactHandle: @@ -1440,6 +1477,28 @@ def async_message_get_contents(message: AsynchronousMessage) -> MessageContents: raise NotImplementedError +def async_message_generate_contents( + message: AsynchronousMessage, +) -> MessageContents | None: + """ + Get the message contents of an `AsynchronousMessage` as a `MessageContents` pointer. + + This function differs from `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. + + [Rust + `pactffi_async_message_generate_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_generate_contents) + + If the message contents are missing, this function will return `None`. + """ + return MessageContents( + lib.pactffi_async_message_generate_contents(message._ptr), + owned=False, + ) + + def async_message_get_contents_str(message: AsynchronousMessage) -> str: """ Get the message contents of an `AsynchronousMessage` in string form. @@ -3037,6 +3096,29 @@ def pact_message_iter_next(iter: PactMessageIterator) -> Message: return Message(ptr) +def pact_async_message_iter_next(iter: PactAsyncMessageIterator) -> AsynchronousMessage: + """ + Get the next asynchronous message from the iterator. + + [Rust + `pactffi_pact_async_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_async_message_iter_next) + """ + ptr = lib.pactffi_pact_async_message_iter_next(iter._ptr) + if ptr == ffi.NULL: + raise StopIteration + return AsynchronousMessage(ptr, owned=True) + + +def pact_async_message_iter_delete(iter: PactAsyncMessageIterator) -> None: + """ + Free the iterator when you're done using it. + + [Rust + `pactffi_pact_async_message_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_async_message_iter_delete) + """ + lib.pactffi_pact_async_message_iter_delete(iter._ptr) + + def pact_sync_message_iter_next(iter: PactSyncMessageIterator) -> SynchronousMessage: """ Get the next synchronous request/response message from the V4 pact. @@ -3584,229 +3666,6 @@ def message_metadata_pair_delete(pair: MessageMetadataPair) -> None: raise NotImplementedError -def message_pact_new_from_json(file_name: str, json_str: str) -> MessagePact: - """ - Construct a new `MessagePact` from the JSON string. - - The provided file name is used when generating error messages. - - [Rust - `pactffi_message_pact_new_from_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_new_from_json) - - # Safety - - The `file_name` and `json_str` parameters must both be valid UTF-8 encoded - strings. - - # Error Handling - - On error, this function will return a null pointer. - """ - raise NotImplementedError - - -def message_pact_delete(message_pact: MessagePact) -> None: - """ - Delete the `MessagePact` being pointed to. - - [Rust - `pactffi_message_pact_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_delete) - """ - raise NotImplementedError - - -def message_pact_get_consumer(message_pact: MessagePact) -> Consumer: - """ - Get a pointer to the Consumer struct inside the MessagePact. - - This is a mutable borrow: The caller may mutate the Consumer through this - pointer. - - [Rust - `pactffi_message_pact_get_consumer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_get_consumer) - - # Safety - - This function is safe. - - # Error Handling - - This function will only fail if it is passed a NULL pointer. In the case of - error, a NULL pointer will be returned. - """ - raise NotImplementedError - - -def message_pact_get_provider(message_pact: MessagePact) -> Provider: - """ - Get a pointer to the Provider struct inside the MessagePact. - - This is a mutable borrow: The caller may mutate the Provider through this - pointer. - - [Rust - `pactffi_message_pact_get_provider`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_get_provider) - - # Safety - - This function is safe. - - # Error Handling - - This function will only fail if it is passed a NULL pointer. In the case of - error, a NULL pointer will be returned. - """ - raise NotImplementedError - - -def message_pact_get_message_iter( - message_pact: MessagePact, -) -> MessagePactMessageIterator: - r""" - Get an iterator over the messages of a message pact. - - [Rust - `pactffi_message_pact_get_message_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_get_message_iter) - - # Safety - - This iterator carries a pointer to the message pact, and must not outlive - the message pact. - - The message pact messages also must not be modified during iteration. If - they are, the old iterator must be deleted and a new iterator created. - - # 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. - """ - raise NotImplementedError - - -def message_pact_message_iter_next(iter: MessagePactMessageIterator) -> Message: - """ - Get the next message from the message pact. - - [Rust - `pactffi_message_pact_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_message_iter_next) - - # Safety - - This function is safe. - - # Error Handling - - This function will return a NULL pointer if passed a NULL pointer or if an - error occurs. - """ - raise NotImplementedError - - -def message_pact_message_iter_delete(iter: MessagePactMessageIterator) -> None: - """ - Delete the iterator. - - [Rust - `pactffi_message_pact_message_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_message_iter_delete) - """ - raise NotImplementedError - - -def message_pact_find_metadata(message_pact: MessagePact, key1: str, key2: str) -> str: - r""" - Get a copy of the metadata value indexed by `key1` and `key2`. - - [Rust - `pactffi_message_pact_find_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_find_metadata) - - # Safety - - Since it is a copy, the returned string may safely outlive the `Message`. - - The returned string must be deleted with `pactffi_string_delete`. - - The returned pointer will be NULL if the metadata does not contain the given - key, or if an error occurred. - - # Error Handling - - On failure, this function will return a NULL pointer. - - This function may fail if the provided `key1` or `key2` strings contains - invalid UTF-8, or if the Rust string contains embedded null ('\0') bytes. - """ - raise NotImplementedError - - -def message_pact_get_metadata_iter( - message_pact: MessagePact, -) -> MessagePactMetadataIterator: - r""" - Get an iterator over the metadata of a message pact. - - [Rust - `pactffi_message_pact_get_metadata_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_get_metadata_iter) - - # Safety - - This iterator carries a pointer to the message pact, and must not outlive - the message pact. - - The message pact metadata also must not be modified during iteration. If it - is, the old iterator must be deleted and a new iterator created. - - # 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. - """ - raise NotImplementedError - - -def message_pact_metadata_iter_next( - iter: MessagePactMetadataIterator, -) -> MessagePactMetadataTriple: - """ - Get the next triple out of the iterator, if possible. - - [Rust - `pactffi_message_pact_metadata_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_metadata_iter_next) - - # Safety - - This operation is invalid if the underlying data has been changed during - iteration. - - # Error Handling - - Returns null if no next element is present. - """ - raise NotImplementedError - - -def message_pact_metadata_iter_delete(iter: MessagePactMetadataIterator) -> None: - """ - Free the metadata iterator when you're done using it. - - [Rust `pactffi_message_pact_metadata_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_metadata_iter_delete) - """ - raise NotImplementedError - - -def message_pact_metadata_triple_delete(triple: MessagePactMetadataTriple) -> None: - """ - Free a triple returned from `pactffi_message_pact_metadata_iter_next`. - - [Rust `pactffi_message_pact_metadata_triple_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_metadata_triple_delete) - """ - raise NotImplementedError - - def provider_get_name(provider: Provider) -> str: r""" Get a copy of this provider's name. @@ -4156,6 +4015,36 @@ def sync_message_get_request_contents(message: SynchronousMessage) -> MessageCon raise NotImplementedError +def sync_message_generate_request_contents( + message: SynchronousMessage, +) -> MessageContents: + """ + Get the request contents of an `SynchronousMessage` as a `MessageContents`. + + 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. + + [Rust + `pactffi_sync_message_generate_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_request_contents) + + # Safety + + The data pointed to by the pointer this function returns will be deleted + when the message is deleted. Trying to use if after the message is deleted + will result in undefined behaviour. + + # Error Handling + + If the message is NULL, returns NULL. + """ + return MessageContents( + lib.pactffi_sync_message_generate_request_contents(message._ptr), + owned=False, + ) + + def sync_message_get_number_responses(message: SynchronousMessage) -> int: """ Get the number of response messages in the `SynchronousMessage`. @@ -4346,6 +4235,38 @@ def sync_message_get_response_contents( raise NotImplementedError +def sync_message_generate_response_contents( + message: SynchronousMessage, + index: int, +) -> MessageContents: + """ + Get the response contents of an `SynchronousMessage` as a `MessageContents`. + + This function differs from + `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. + + [Rust + `pactffi_sync_message_generate_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_response_contents) + + # Safety + + The data pointed to by the pointer this function returns will be deleted + when the message is deleted. Trying to use if after the message is deleted + will result in undefined behaviour. + + # Error Handling + + If the message is NULL or the index is not valid, returns NULL. + """ + return MessageContents( + lib.pactffi_sync_message_generate_response_contents(message._ptr, index), + owned=False, + ) + + def sync_message_get_description(message: SynchronousMessage) -> str: r""" Get a copy of the description. @@ -5275,6 +5196,24 @@ def with_query_parameter_v2( ) ``` + For query parameters with no value, two distinct formats are provided: + + 1. Parameters with blank values, as specified by `?foo=&bar=`, require an + empty string: + + ```python + with_query_parameter_v2(handle, "foo", 0, "") + with_query_parameter_v2(handle, "bar", 0, "") + ``` + + 2. Parameters with no associated value, as specified by `?foo&bar`, require + a NULL pointer: + + ```python + with_query_parameter_v2(handle, "foo", 0, None) + with_query_parameter_v2(handle, "bar", 0, None) + ``` + Args: interaction: Handle to the Interaction. @@ -5384,6 +5323,74 @@ def with_pact_metadata( raise RuntimeError(msg) +def with_metadata( + interaction: InteractionHandle, + key: str, + value: str, + part: InteractionPart, +) -> None: + r""" + Adds metadata to the interaction. + + Metadata is only relevant for message interactions to provide additional + information about the message, such as the queue name, message type, tags, + timestamps, etc. + + * `key` - metadata key + * `value` - metadata value, supports JSON structures with matchers and + generators. Passing a `NULL` point will remove the metadata key instead. + * `part` - the part of the interaction to add the metadata to (only + relevant for synchronous message interactions). + + Returns `true` if the metadata was added successfully, `false` otherwise. + + To include matching rules for the value, include the matching rule JSON + format with the value as a single JSON document. I.e. + + ```python with_metadata( + handle, "TagData", json.dumps({ + "value": {"ID": "sjhdjkshsdjh", "weight": 100.5}, + "pact:matcher:type": "type", + }), + ) + ``` + + See + [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md) + + # Note + + For HTTP interactions, use [`with_header_v2`][pact.v3.ffi.with_header_v2] + instead. This function will not have any effect on HTTP interactions and + returns `false`. + + For synchronous message interactions, the `part` parameter is required to + specify whether the metadata should be added to the request or response + part. For responses which can have multiple messages, the metadata will be + set on all response messages. This also requires for responses to have been + defined in the interaction. + + The [`with_body`][pact.v3.ffi.with_body] will also contribute to the + metadata of the message (both sync and async) by setting the key + `contentType` with the content type of the message. + + # Safety + + The key and value parameters must be valid pointers to NULL terminated + strings, or `NULL` for the value parameter if the metadata key should be + removed. + """ + success: bool = lib.pactffi_with_metadata( + interaction._ref, + key.encode("utf-8"), + value.encode("utf-8"), + part.value, + ) + if not success: + msg = f"Failed to set metadata for {interaction} with {key}={value}" + raise RuntimeError(msg) + + def with_header_v2( interaction: InteractionHandle, part: InteractionPart, @@ -5766,6 +5773,44 @@ def with_matching_rules( raise RuntimeError(msg) +def with_generators( + interaction: InteractionHandle, + part: InteractionPart, + generators: str, +) -> None: + """ + Add generators to the interaction. + + [Rust + `pactffi_with_generators`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_generators) + + This function can be called multiple times, in which case the generators + will be combined (provide they don't clash). + + For synchronous messages which allow multiple responses, the generators will + be added to all the responses. + + Args: + interaction: + Handle to the Interaction. + + part: + Request or response part (if applicable). + + generators: + JSON string of the generators to add to the interaction. + + """ + success: bool = lib.pactffi_with_generators( + interaction._ref, + part.value, + generators.encode("utf-8"), + ) + if not success: + msg = f"Unable to set generators for {interaction}." + raise RuntimeError(msg) + + def with_multipart_file_v2( # noqa: PLR0913 interaction: InteractionHandle, part: InteractionPart, @@ -5989,9 +6034,9 @@ def pact_handle_get_message_iter(pact: PactHandle) -> PactMessageIterator: return PactMessageIterator(lib.pactffi_pact_handle_get_message_iter(pact._ref)) -def pact_handle_get_sync_message_iter(pact: PactHandle) -> PactSyncMessageIterator: +def pact_handle_get_async_message_iter(pact: PactHandle) -> PactAsyncMessageIterator: r""" - Get an iterator over all the synchronous messages of the Pact. + Get an iterator over all the asynchronous messages of the Pact. The returned iterator needs to be freed with `pactffi_pact_sync_message_iter_delete`. @@ -6010,20 +6055,20 @@ def pact_handle_get_sync_message_iter(pact: PactHandle) -> PactSyncMessageIterat This function may fail if any of the Rust strings contain embedded null ('\0') bytes. """ - return PactSyncMessageIterator( - lib.pactffi_pact_handle_get_sync_message_iter(pact._ref), + return PactAsyncMessageIterator( + lib.pactffi_pact_handle_get_async_message_iter(pact._ref), ) -def pact_handle_get_sync_http_iter(pact: PactHandle) -> PactSyncHttpIterator: +def pact_handle_get_sync_message_iter(pact: PactHandle) -> PactSyncMessageIterator: r""" - Get an iterator over all the synchronous HTTP request/response interactions. + Get an iterator over all the synchronous messages of the Pact. The returned iterator needs to be freed with - `pactffi_pact_sync_http_iter_delete`. + `pactffi_pact_sync_message_iter_delete`. [Rust - `pactffi_pact_handle_get_sync_http_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_sync_http_iter) + `pactffi_pact_handle_get_sync_message_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_sync_message_iter) # Safety @@ -6036,250 +6081,33 @@ def pact_handle_get_sync_http_iter(pact: PactHandle) -> PactSyncHttpIterator: This function may fail if any of the Rust strings contain embedded null ('\0') bytes. """ - return PactSyncHttpIterator(lib.pactffi_pact_handle_get_sync_http_iter(pact._ref)) - - -def new_message_pact(consumer_name: str, provider_name: str) -> MessagePactHandle: - """ - Creates a new Pact Message model and returns a handle to it. - - [Rust - `pactffi_new_message_pact`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_new_message_pact) - - * `consumer_name` - The name of the consumer for the pact. - * `provider_name` - The name of the provider for the pact. - - Returns a new `MessagePactHandle`. The handle will need to be freed with the - `pactffi_free_message_pact_handle` function to release its resources. - """ - raise NotImplementedError - - -def new_message(pact: MessagePactHandle, description: str) -> MessageHandle: - """ - Creates a new Message and returns a handle to it. - - [Rust - `pactffi_new_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_new_message) - - * `description` - The message description. It needs to be unique for each - Message. - - Returns a new `MessageHandle`. - """ - raise NotImplementedError - - -def message_expects_to_receive(message: MessageHandle, description: str) -> None: - """ - Sets the description for the Message. - - [Rust - `pactffi_message_expects_to_receive`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_expects_to_receive) - - * `description` - The message description. It needs to be unique for each - message. - """ - raise NotImplementedError - - -def message_given(message: MessageHandle, description: str) -> None: - """ - Adds a provider state to the Interaction. - - [Rust - `pactffi_message_given`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_given) - - * `description` - The provider state description. It needs to be unique for - each message - """ - raise NotImplementedError - - -def message_given_with_param( - message: MessageHandle, - description: str, - name: str, - value: str, -) -> None: - """ - Adds a provider state to the Message with a parameter key and value. - - [Rust - `pactffi_message_given_with_param`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_given_with_param) - - * `description` - The provider state description. It needs to be unique. - * `name` - Parameter name. - * `value` - Parameter value. - """ - raise NotImplementedError - - -def message_with_contents( - message_handle: MessageHandle, - content_type: str, - body: List[int], - size: int, -) -> None: - """ - Adds the contents of the Message. - - [Rust - `pactffi_message_with_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_with_contents) - - Accepts JSON, binary and other payload types. Binary data will be base64 - encoded when serialised. - - Note: For text bodies (plain text, JSON or XML), you can pass in a C string - (NULL terminated) and the size of the body is not required (it will be - ignored). For binary bodies, you need to specify the number of bytes in the - body. - - * `content_type` - The content type of the body. Defaults to `text/plain`, - supports JSON structures with matchers and binary data. - * `body` - The body contents as bytes. For text payloads (JSON, XML, etc.), - a C string can be used and matching rules can be embedded in the body. - * `content_type` - Expected content type (e.g. application/json, - application/octet-stream) - * `size` - number of bytes in the message body to read. This is not required - for text bodies (JSON, XML, etc.). - """ - raise NotImplementedError - - -def message_with_metadata(message_handle: MessageHandle, key: str, value: str) -> None: - """ - Adds expected metadata to the Message. - - [Rust - `pactffi_message_with_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_with_metadata) - - * `key` - metadata key - * `value` - metadata value. - """ - raise NotImplementedError - - -def message_with_metadata_v2( - message_handle: MessageHandle, - key: str, - value: str, -) -> None: - """ - Adds expected metadata to the Message. - - [Rust - `pactffi_message_with_metadata_v2`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_with_metadata_v2) - - Args: - message_handle: - Handle to the Message. - - key: - Metadata key. - - value: - Metadata value. - - This may be a simple string in which case it will be used as-is, or - it may be a [JSON matching - rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). - - To include matching rules for the metadata, include the matching rule JSON - format with the value as a single JSON document. I.e. - - ```python - message_with_metadata_v2( - handle, - "contentType", - json.dumps({ - "pact:matcher:type": "regex", - "regex": "text/.*", - }), + return PactSyncMessageIterator( + lib.pactffi_pact_handle_get_sync_message_iter(pact._ref), ) - ``` - See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). - """ - raise NotImplementedError +def pact_handle_get_sync_http_iter(pact: PactHandle) -> PactSyncHttpIterator: + r""" + Get an iterator over all the synchronous HTTP request/response interactions. -def message_reify(message_handle: MessageHandle) -> OwnedString: - """ - Reifies the given message. + The returned iterator needs to be freed with + `pactffi_pact_sync_http_iter_delete`. [Rust - `pactffi_message_reify`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_reify) - - Reification is the process of stripping away any matchers, and returning the - original contents. + `pactffi_pact_handle_get_sync_http_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_sync_http_iter) # Safety - The returned string needs to be deallocated with the `free_string` function. - This function must only ever be called from a foreign language. Calling it - from a Rust function that has a Tokio runtime in its call stack can result - in a deadlock. - """ - raise NotImplementedError - - -def write_message_pact_file( - pact: MessagePactHandle, - directory: str, - *, - overwrite: bool, -) -> int: - """ - External interface to write out the message pact file. - - This function should be called if all the consumer tests have passed. The - directory to write the file to is passed as the second parameter. If a NULL - pointer is passed, the current working directory is used. - - [Rust - `pactffi_write_message_pact_file`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_write_message_pact_file) - - If overwrite is true, the file will be overwritten with the contents of the - current pact. Otherwise, it will be merged with any existing pact file. - - Returns 0 if the pact file was successfully written. Returns a positive code - if the file can not be written, or there is no mock server running on that - port or the function panics. - - # Errors - - Errors are returned as positive values. - - | Error | Description | - |-------|-------------| - | 1 | The pact file was not able to be written | - | 2 | The message pact for the given handle was not found | - """ - raise NotImplementedError - - -def with_message_pact_metadata( - pact: MessagePactHandle, - namespace_: str, - name: str, - value: str, -) -> None: - """ - Sets the additional metadata on the Pact file. + The iterator contains a copy of the Pact, so it is always safe to use. - Common uses are to add the client library details such as the name and - version + # Error Handling - [Rust - `pactffi_with_message_pact_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_message_pact_metadata) + On failure, this function will return a NULL pointer. - * `pact` - Handle to a Pact model - * `namespace` - the top level metadat key to set any key values on - * `name` - the key to set - * `value` - the value to set + This function may fail if any of the Rust strings contain embedded null + ('\0') bytes. """ - raise NotImplementedError + return PactSyncHttpIterator(lib.pactffi_pact_handle_get_sync_http_iter(pact._ref)) def pact_handle_write_file( @@ -6347,24 +6175,6 @@ def free_pact_handle(pact: PactHandle) -> None: raise RuntimeError(msg) -def free_message_pact_handle(pact: MessagePactHandle) -> int: - """ - Delete a Pact handle and free the resources used by it. - - [Rust - `pactffi_free_message_pact_handle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_free_message_pact_handle) - - # Error Handling - - On failure, this function will return a positive integer value. - - * `1` - The handle is not valid or does not refer to a valid Pact. Could be - that it was previously deleted. - - """ - raise NotImplementedError - - def verify(args: str) -> int: """ External interface to verifier a provider. @@ -6912,9 +6722,11 @@ def verifier_broker_source_with_selectors( # noqa: PLR0913 password.encode("utf-8") if password else ffi.NULL, token.encode("utf-8") if token else ffi.NULL, enable_pending, - include_wip_pacts_since.isoformat().encode("utf-8") - if include_wip_pacts_since - else ffi.NULL, + ( + include_wip_pacts_since.isoformat().encode("utf-8") + if include_wip_pacts_since + else ffi.NULL + ), [ffi.new("char[]", t.encode("utf-8")) for t in provider_tags], len(provider_tags), provider_branch.encode("utf-8") if provider_branch else ffi.NULL, diff --git a/src/pact/v3/interaction/_base.py b/src/pact/v3/interaction/_base.py index 1b375d29a..d840c4a58 100644 --- a/src/pact/v3/interaction/_base.py +++ b/src/pact/v3/interaction/_base.py @@ -295,6 +295,73 @@ def with_binary_body( ) return self + def with_metadata( + self, + __metadata: dict[str, str] | None = None, + __part: Literal["Request", "Response"] | None = None, + /, + **kwargs: str, + ) -> Self: + """ + Set metadata for the interaction. + + This function may either be called with a single dictionary of metadata, + or with keyword arguments that are the key-value pairs of the metadata + (or a combination therefore): + + ```python + interaction.with_metadata({"key": "value", "key two": "value two"}) + interaction.with_metadata(foo="bar", baz="qux") + ``` + + The value of `None` will remove the metadata key from the interaction. + This is distinct from using an empty string or a string containing the + JSON `null` value, which will set the metadata key to an empty string or + the JSON `null` value, respectively. + + !!! note + + There are two special keys which cannot be used as keyword + arguments: `__metadata` and `__part`. Should there ever be a need + to set metadata with one of these keys, they must be passed through + as a dictionary: + + ```python + interaction.with_metadata({"__metadata": "value", "__part": 1}) + ``` + + Args: + ___metadata: + Dictionary of metadata keys and associated values. + + __part: + Whether the metadata should be added to the request or the + response. If `None`, then the function intelligently determines + whether the body should be added to the request or the response. + + **kwargs: + Additional metadata key-value pairs. + + Returns: + The current instance of the interaction. + """ + part = self._parse_interaction_part(__part) + for k, v in (__metadata or {}).items(): + pact.v3.ffi.with_metadata( + self._handle, + k, + v, + part, + ) + for k, v in kwargs.items(): + pact.v3.ffi.with_metadata( + self._handle, + k, + v, + part, + ) + return self + def with_multipart_file( # noqa: PLR0913 self, part_name: str, @@ -471,3 +538,36 @@ def with_matching_rules( rules, ) return self + + def with_generators( + self, + generators: dict[str, Any] | str, + part: Literal["Request", "Response"] | None = None, + ) -> Self: + """ + Add generators to the interaction. + + Generators are used to adjust how parts of the request or response are + generated when the Pact is being tested. This can be useful for fields + that vary each time the request is made, such as a timestamp. + + Args: + generators: + Generators to add to the interaction. This must be encodable using + [`json.dumps(...)`][json.dumps], or a string. + + part: + Whether the generators should be added to the request or the + response. If `None`, then the function intelligently determines + whether the generators should be added to the request or the + response. + """ + if isinstance(generators, dict): + generators = json.dumps(generators) + + pact.v3.ffi.with_generators( + self._handle, + self._parse_interaction_part(part), + generators, + ) + return self From 088a9bb00af38434b9a3bfe454d2992e40d75270 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 21 Jun 2024 14:43:10 +1000 Subject: [PATCH 03/23] feat(v3): add enum type aliases Define some type aliases for some enums. As a result of this, functions which take a subclass of an `Enum` can be, fairly straightforwardly, adapted to allow strings of the enum values. Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index df18845af..6294124b3 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -89,7 +89,7 @@ import typing import warnings from enum import Enum -from typing import TYPE_CHECKING, Any, List +from typing import TYPE_CHECKING, Any, List, Literal from pact.v3._ffi import ffi, lib # type: ignore[import] @@ -103,6 +103,43 @@ logger = logging.getLogger(__name__) +################################################################################ +# Type aliases +################################################################################ +# The following type aliases provide a nicer interface for end-users of the +# library, especially when it comes to [`Enum`][Enum] classes which offers +# support for string literals as alternative values. + +GeneratorCategoryOptions = Literal[ + "METHOD", "method", + "PATH", "path", + "HEADER", "header", + "QUERY", "query", + "BODY", "body", + "STATUS", "status", + "METADATA", "metadata", +] # fmt: skip +""" +Generator Category Options. + +Type alias for the string literals which represent the Generator Category +Options. +""" + +MatchingRuleCategoryOptions = Literal[ + "METHOD", "method", + "PATH", "path", + "HEADER", "header", + "QUERY", "query", + "BODY", "body", + "STATUS", "status", + "CONTENTS", "contents", + "METADATA", "metadata", +] # fmt: skip + +################################################################################ +# Classes +################################################################################ # The follow types are classes defined in the Rust code. Ultimately, a Python # alternative should be implemented, but for now, the follow lines only serve # to inform the type checker of the existence of these types. From f25f8f3788bf284af5ca28477981f9c3ffe96338 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Sat, 22 Jun 2024 20:00:48 +1000 Subject: [PATCH 04/23] chore(ffi): disable private usage lint Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 6294124b3..9b96b0866 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -80,6 +80,9 @@ # ruff: noqa: SLF001 # private-member-access, as we need access to other handles' internal # references, without exposing them to the user. +# pyright: reportPrivateUsage=false +# Ignore private member access, as we frequently need to use the +# object's underlying pointer stored in `_ptr`. from __future__ import annotations From 8bb0ef9ccda5baf03c370e41e4fdd2b7493a9a19 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 21 Jun 2024 15:19:02 +1000 Subject: [PATCH 05/23] chore(ffi): implement AsynchronousMessage Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 119 ++++++++++++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 9b96b0866..5b3e65bca 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -93,6 +93,7 @@ import warnings from enum import Enum from typing import TYPE_CHECKING, Any, List, Literal +from typing import Generator as GeneratorType from pact.v3._ffi import ffi, lib # type: ignore[import] @@ -148,7 +149,72 @@ # to inform the type checker of the existence of these types. -class AsynchronousMessage: ... +class AsynchronousMessage: + def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None: + """ + Initialise a new Asynchronous Message. + + Args: + ptr: + CFFI data structure. + + owned: + Whether the message is owned by something else or not. This + determines whether the message should be freed when the Python + object is destroyed. + """ + if ffi.typeof(ptr).cname != "struct AsynchronousMessage *": + msg = ( + "ptr must be a struct AsynchronousMessage, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + self._owned = owned + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "AsynchronousMessage" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"AsynchronousMessage({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the AsynchronousMessage. + """ + if not self._owned: + async_message_delete(self) + + @property + def description(self) -> str: + """ + Description of this message interaction. + + This needs to be unique in the pact file. + """ + return async_message_get_description(self) + + def provider_states(self) -> GeneratorType[ProviderState, None, None]: + """ + Optional provider state for the interaction. + """ + yield from async_message_get_provider_state_iter(self) + return # Ensures that the parent object outlives the generator + + @property + def contents(self) -> MessageContents | None: + """ + The contents of the message. + + This may be `None` if the message has no contents. + """ + return async_message_generate_contents(self) class Consumer: ... @@ -1494,27 +1560,19 @@ def async_message_delete(message: AsynchronousMessage) -> None: [Rust `pactffi_async_message_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_delete) """ - raise NotImplementedError + lib.pactffi_async_message_delete(message._ptr) -def async_message_get_contents(message: AsynchronousMessage) -> MessageContents: +def async_message_get_contents(message: AsynchronousMessage) -> MessageContents | None: """ Get the message contents of an `AsynchronousMessage` as a `MessageContents` pointer. [Rust `pactffi_async_message_get_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_contents) - # Safety - - The data pointed to by the pointer this function returns will be deleted - when the message is deleted. Trying to use if after the message is deleted - will result in undefined behaviour. - - # Error Handling - - If the message is NULL, returns NULL. + If the message contents are missing, this function will return `None`. """ - raise NotImplementedError + return MessageContents(lib.pactffi_async_message_get_contents(message._ptr)) def async_message_generate_contents( @@ -1673,21 +1731,14 @@ def async_message_get_description(message: AsynchronousMessage) -> str: [Rust `pactffi_async_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_description) - # Safety - - The returned string must be deleted with `pactffi_string_delete`. - - Since it is a copy, the returned string may safely outlive the - `AsynchronousMessage`. - - # Errors - - On failure, this function will return a NULL pointer. - - This function may fail if the Rust string contains embedded null ('\0') - bytes. + Raises: + RuntimeError: If the description cannot be retrieved. """ - raise NotImplementedError + ptr = lib.pactffi_async_message_get_description(message._ptr) + if ptr == ffi.NULL: + msg = "Unable to get the description from the message." + raise RuntimeError(msg) + return OwnedString(ptr) def async_message_set_description( @@ -1738,7 +1789,11 @@ def async_message_get_provider_state( This function may fail if the index requested is out of bounds, or if any of the Rust strings contain embedded null ('\0') bytes. """ - raise NotImplementedError + ptr = lib.pactffi_async_message_get_provider_state(message._ptr, index) + if ptr == ffi.NULL: + msg = "Unable to get the provider state from the message." + raise RuntimeError(msg) + return ProviderState(ptr) def async_message_get_provider_state_iter( @@ -1752,12 +1807,10 @@ def async_message_get_provider_state_iter( # Safety The underlying data must not change during iteration. - - # Error Handling - - Returns NULL if an error occurs. """ - raise NotImplementedError + return ProviderStateIterator( + lib.pactffi_async_message_get_provider_state_iter(message._ptr) + ) def consumer_get_name(consumer: Consumer) -> str: From f182077b643185d73a9a83d502c05f5e185cfbb0 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 21 Jun 2024 15:42:42 +1000 Subject: [PATCH 06/23] chore(ffi): implement Generator The `Generator` (and other closely related types) are returned by the `MessageContents` type. These will generally not be used, but may prove to be useful in a few select circumstances. Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 196 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 186 insertions(+), 10 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 5b3e65bca..8a31f3a74 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -220,13 +220,174 @@ def contents(self) -> MessageContents | None: class Consumer: ... -class Generator: ... +class Generator: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a generator value. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct Generator *": + msg = "ptr must be a struct Generator, got" f" {ffi.typeof(ptr).cname}" + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "Generator" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"Generator({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the Generator. + """ + + @property + def json(self) -> dict[str, Any]: + """ + Dictionary representation of the generator. + """ + return json.loads(generator_to_json(self)) + + def generate_string(self, context: dict[str, Any] | None = None) -> str: + """ + Generate a string from the generator. + + Args: + context: + JSON payload containing any generator context. For example: + + - The context for a `MockServerURL` generator should contain + details about the running mock server. + - The context for a `ProviderStateGenerator` should contain + the values returned from the provider state callback + function. + """ + return generator_generate_string(self, json.dumps(context or {})) + + def generate_integer(self, context: dict[str, Any] | None = None) -> int: + """ + Generate an integer from the generator. + + Args: + context: + JSON payload containing any generator context. For example: + + - The context for a `ProviderStateGenerator` should contain + the values returned from the provider state callback + function. + """ + return generator_generate_integer(self, json.dumps(context or {})) + + +class GeneratorCategoryIterator: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new generator category iterator. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct GeneratorCategoryIterator *": + msg = ( + "ptr must be a struct GeneratorCategoryIterator, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "GeneratorCategoryIterator" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"GeneratorCategoryIterator({self._ptr!r})" + def __del__(self) -> None: + """ + Destructor for the GeneratorCategoryIterator. + """ + generators_iter_delete(self) -class GeneratorCategoryIterator: ... + def __iter__(self) -> Self: + """ + Return the iterator itself. + """ + return self + def __next__(self) -> GeneratorKeyValuePair: + """ + Get the next generator category from the iterator. + """ + return generators_iter_next(self) -class GeneratorKeyValuePair: ... + +class GeneratorKeyValuePair: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new key-value generator pair. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct GeneratorKeyValuePair *": + msg = ( + "ptr must be a struct GeneratorKeyValuePair, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "GeneratorKeyValuePair" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"GeneratorKeyValuePair({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the GeneratorKeyValuePair. + """ + generators_iter_pair_delete(self) + + @property + def path(self) -> str: + """ + Generator path. + """ + s = ffi.string(self._ptr.path) # type: ignore[attr-defined] + if isinstance(s, bytes): + s = s.decode("utf-8") + return s + + @property + def generator(self) -> Generator: + """ + Generator value. + """ + return Generator(self._ptr.generator) # type: ignore[attr-defined] class HttpRequest: ... @@ -2175,7 +2336,9 @@ def message_contents_get_generators_iter( On failure, this function will return a NULL pointer. """ - raise NotImplementedError + return GeneratorCategoryIterator( + lib.pactffi_message_contents_get_generators_iter(contents, category) + ) def request_contents_get_generators_iter( @@ -2577,7 +2740,7 @@ def generator_to_json(generator: Generator) -> str: This function will fail if it is passed a NULL pointer, or the owner of the generator has been deleted. """ - raise NotImplementedError + return OwnedString(lib.pactffi_generator_to_json(generator._ptr)) def generator_generate_string(generator: Generator, context_json: str) -> str: @@ -2595,7 +2758,14 @@ def generator_generate_string(generator: Generator, context_json: str) -> str: If anything goes wrong, it will return a NULL pointer. """ - raise NotImplementedError + ptr = lib.pactffi_generator_generate_string( + generator._ptr, + context_json.encode("utf-8"), + ) + s = ffi.string(ptr) + if isinstance(s, bytes): + s = s.decode("utf-8") + return s def generator_generate_integer(generator: Generator, context_json: str) -> int: @@ -2612,7 +2782,10 @@ def generator_generate_integer(generator: Generator, context_json: str) -> int: If anything goes wrong or the generator is not a type that can generate an integer value, it will return a zero value. """ - raise NotImplementedError + return lib.pactffi_generator_generate_integer( + generator._ptr, + context_json.encode("utf-8"), + ) def generators_iter_delete(iter: GeneratorCategoryIterator) -> None: @@ -2622,7 +2795,7 @@ def generators_iter_delete(iter: GeneratorCategoryIterator) -> None: [Rust `pactffi_generators_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generators_iter_delete) """ - raise NotImplementedError + lib.pactffi_generators_iter_delete(iter._ptr) def generators_iter_next(iter: GeneratorCategoryIterator) -> GeneratorKeyValuePair: @@ -2644,7 +2817,10 @@ def generators_iter_next(iter: GeneratorCategoryIterator) -> GeneratorKeyValuePa If no further data is present, returns NULL. """ - raise NotImplementedError + ptr = lib.pactffi_generators_iter_next(iter._ptr) + if ptr == ffi.NULL: + raise StopIteration + return GeneratorKeyValuePair(ptr) def generators_iter_pair_delete(pair: GeneratorKeyValuePair) -> None: @@ -2654,7 +2830,7 @@ def generators_iter_pair_delete(pair: GeneratorKeyValuePair) -> None: [Rust `pactffi_generators_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generators_iter_pair_delete) """ - raise NotImplementedError + lib.pactffi_generators_iter_pair_delete(pair._ptr) def sync_http_new() -> SynchronousHttp: From 901cfc57cb9e22f49b73b6a8c3e68952a7d31b1a Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 21 Jun 2024 15:57:43 +1000 Subject: [PATCH 07/23] chore(ffi): implement MatchingRule Along with associated types Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 140 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 133 insertions(+), 7 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 8a31f3a74..17ff11888 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -427,10 +427,86 @@ def __repr__(self) -> str: return f"InteractionHandle({self._ref!r})" -class MatchingRule: ... +class MatchingRule: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new key-value generator pair. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct MatchingRule *": + msg = "ptr must be a struct MatchingRule, got" f" {ffi.typeof(ptr).cname}" + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "MatchingRule" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"MatchingRule({self._ptr!r})" + + @property + def json(self) -> dict[str, Any]: + """ + Dictionary representation of the matching rule. + """ + return json.loads(matching_rule_to_json(self)) + + +class MatchingRuleCategoryIterator: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new key-value generator pair. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct MatchingRuleCategoryIterator *": + msg = ( + "ptr must be a struct MatchingRuleCategoryIterator, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "MatchingRuleCategoryIterator" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"MatchingRuleCategoryIterator({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the MatchingRuleCategoryIterator. + """ + matching_rules_iter_delete(self) + def __iter__(self) -> Self: + """ + Return the iterator itself. + """ + return self -class MatchingRuleCategoryIterator: ... + def __next__(self) -> MatchingRuleKeyValuePair: + """ + Get the next generator category from the iterator. + """ + return matching_rules_iter_next(self) class MatchingRuleDefinitionResult: ... @@ -439,7 +515,57 @@ class MatchingRuleDefinitionResult: ... class MatchingRuleIterator: ... -class MatchingRuleKeyValuePair: ... +class MatchingRuleKeyValuePair: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new key-value generator pair. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct MatchingRuleKeyValuePair *": + msg = ( + "ptr must be a struct MatchingRuleKeyValuePair, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "MatchingRuleKeyValuePair" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"MatchingRuleKeyValuePair({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the MatchingRuleKeyValuePair. + """ + matching_rules_iter_pair_delete(self) + + @property + def path(self) -> str: + """ + Matching Rule path. + """ + s = ffi.string(self._ptr.path) # type: ignore[attr-defined] + if isinstance(s, bytes): + s = s.decode("utf-8") + return s + + @property + def matching_rule(self) -> MatchingRule: + """ + Matching Rule value. + """ + return MatchingRule(self._ptr.matching_rule) # type: ignore[attr-defined] class MatchingRuleResult: ... @@ -3474,7 +3600,7 @@ def matching_rule_to_json(rule: MatchingRule) -> str: This function will fail if it is passed a NULL pointer, or the iterator that owns the value of the matching rule has been deleted. """ - raise NotImplementedError + return OwnedString(lib.pactffi_matching_rule_to_json(rule._ptr)) def matching_rules_iter_delete(iter: MatchingRuleCategoryIterator) -> None: @@ -3484,7 +3610,7 @@ def matching_rules_iter_delete(iter: MatchingRuleCategoryIterator) -> None: [Rust `pactffi_matching_rules_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rules_iter_delete) """ - raise NotImplementedError + lib.pactffi_matching_rules_iter_delete(iter._ptr) def matching_rules_iter_next( @@ -3508,7 +3634,7 @@ def matching_rules_iter_next( If no further data is present, returns NULL. """ - raise NotImplementedError + return MatchingRuleKeyValuePair(lib.pactffi_matching_rules_iter_next(iter._ptr)) def matching_rules_iter_pair_delete(pair: MatchingRuleKeyValuePair) -> None: @@ -3518,7 +3644,7 @@ def matching_rules_iter_pair_delete(pair: MatchingRuleKeyValuePair) -> None: [Rust `pactffi_matching_rules_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rules_iter_pair_delete) """ - raise NotImplementedError + lib.pactffi_matching_rules_iter_pair_delete(pair._ptr) def message_new() -> Message: From 1d504d05cfadb40c3e2ff482cdc25b9f4556438a Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Sat, 22 Jun 2024 21:58:09 +1000 Subject: [PATCH 08/23] chore(ffi): remove old message and message handle The `InteractionHandle` is the intended way to define interactions, with `Message` and `MessageHandle` being replaced by the former. Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 476 --------------------------------------------- 1 file changed, 476 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 17ff11888..dbb0e14a9 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -571,15 +571,9 @@ def matching_rule(self) -> MatchingRule: class MatchingRuleResult: ... -class Message: ... - - class MessageContents: ... -class MessageHandle: ... - - class MessageMetadataIterator: ... @@ -789,57 +783,6 @@ def __next__(self) -> PactInteraction: return pact_interaction_iter_next(self) -class PactMessageIterator: - """ - Iterator over a Pact's asynchronous messages. - """ - - def __init__(self, ptr: cffi.FFI.CData) -> None: - """ - Initialise a new Pact Message Iterator. - - Args: - ptr: - CFFI data structure. - """ - if ffi.typeof(ptr).cname != "struct PactMessageIterator *": - msg = ( - f"ptr must be a struct PactMessageIterator, got {ffi.typeof(ptr).cname}" - ) - raise TypeError(msg) - self._ptr = ptr - - def __str__(self) -> str: - """ - Nice string representation. - """ - return "PactMessageIterator" - - def __repr__(self) -> str: - """ - Debugging representation. - """ - return f"PactMessageIterator({self._ptr!r})" - - def __del__(self) -> None: - """ - Destructor for the Pact Message Iterator. - """ - pact_message_iter_delete(self) - - def __iter__(self) -> Self: - """ - Return the iterator itself. - """ - return self - - def __next__(self) -> Message: - """ - Get the next message from the iterator. - """ - return pact_message_iter_next(self) - - class PactSyncHttpIterator: """ Iterator over a Pact's synchronous HTTP interactions. @@ -1437,18 +1380,6 @@ def log_message( ) -def match_message(msg_1: Message, msg_2: Message) -> Mismatches: - """ - Match a pair of messages, producing a collection of mismatches. - - If the messages match, the returned collection will be empty. - - [Rust - `pactffi_match_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_match_message) - """ - raise NotImplementedError - - def mismatches_get_iter(mismatches: Mismatches) -> MismatchesIterator: """ Get an iterator over mismatches. @@ -3398,28 +3329,6 @@ def pact_interaction_as_synchronous_http( raise NotImplementedError -def pact_interaction_as_message(interaction: PactInteraction) -> Message: - """ - Casts this interaction to a `Message` interaction. - - Returns a NULL pointer if the interaction can not be casted to a `Message` - interaction (for instance, it is a http interaction). The returned pointer - must be freed with `pactffi_message_delete` when no longer required. - - [Rust - `pactffi_pact_interaction_as_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_as_message) - - Note that if the interaction is a V4 `AsynchronousMessage`, it will be - converted to a V3 `Message` before being returned. - - # Safety This function is safe as long as the interaction pointer is a valid - pointer. - - # Errors On any error, this function will return a NULL pointer. - """ - raise NotImplementedError - - def pact_interaction_as_asynchronous_message( interaction: PactInteraction, ) -> AsynchronousMessage: @@ -3467,30 +3376,6 @@ def pact_interaction_as_synchronous_message( raise NotImplementedError -def pact_message_iter_delete(iter: PactMessageIterator) -> None: - """ - Free the iterator when you're done using it. - - [Rust - `pactffi_pact_message_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_message_iter_delete) - """ - lib.pactffi_pact_message_iter_delete(iter._ptr) - - -def pact_message_iter_next(iter: PactMessageIterator) -> Message: - """ - Get the next message from the message pact. - - [Rust - `pactffi_pact_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_message_iter_next) - """ - ptr = lib.pactffi_pact_message_iter_next(iter._ptr) - if ptr == ffi.NULL: - raise StopIteration - raise NotImplementedError - return Message(ptr) - - def pact_async_message_iter_next(iter: PactAsyncMessageIterator) -> AsynchronousMessage: """ Get the next asynchronous message from the iterator. @@ -3647,275 +3532,6 @@ def matching_rules_iter_pair_delete(pair: MatchingRuleKeyValuePair) -> None: lib.pactffi_matching_rules_iter_pair_delete(pair._ptr) -def message_new() -> Message: - """ - Get a mutable pointer to a newly-created default message on the heap. - - [Rust - `pactffi_message_new`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_new) - - # Safety - - This function is safe. - - # Error Handling - - Returns NULL on error. - """ - raise NotImplementedError - - -def message_new_from_json( - index: int, - json_str: str, - spec_version: PactSpecification, -) -> Message: - """ - Constructs a `Message` from the JSON string. - - [Rust - `pactffi_message_new_from_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_new_from_json) - - # Safety - - This function is safe. - - # Error Handling - - If the JSON string is invalid or not UTF-8 encoded, returns a NULL. - """ - raise NotImplementedError - - -def message_new_from_body(body: str, content_type: str) -> Message: - """ - Constructs a `Message` from a body with a given content-type. - - [Rust - `pactffi_message_new_from_body`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_new_from_body) - - # Safety - - This function is safe. - - # Error Handling - - If the body or content type are invalid or not UTF-8 encoded, returns NULL. - """ - raise NotImplementedError - - -def message_delete(message: Message) -> None: - """ - Destroy the `Message` being pointed to. - - [Rust - `pactffi_message_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_delete) - """ - raise NotImplementedError - - -def message_get_contents(message: Message) -> OwnedString | None: - """ - Get the contents of a `Message` in string form. - - [Rust - `pactffi_message_get_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_contents) - - # Safety - - The returned string must be deleted with `pactffi_string_delete` and can - outlive the message. This function must only ever be called from a foreign - language. Calling it from a Rust function that has a Tokio runtime in its - call stack can result in a deadlock. - - The returned string can outlive the message. - - # Error Handling - - If the message is NULL, returns NULL. If the body of the message is missing, - then this function also returns NULL. This means there's no mechanism to - differentiate with this function call alone between a NULL message and a - missing message body. - """ - raise NotImplementedError - - -def message_set_contents(message: Message, contents: str, content_type: str) -> None: - """ - Sets the contents of the message. - - [Rust - `pactffi_message_set_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_set_contents) - - # Safety - - The message contents and content type must either be NULL pointers, or point - to valid UTF-8 encoded NULL-terminated strings. Otherwise behaviour is - undefined. - - # Error Handling - - If the contents is a NULL pointer, it will set the message contents as null. - If the content type is a null pointer, or can't be parsed, it will set the - content type as unknown. - """ - raise NotImplementedError - - -def message_get_contents_length(message: Message) -> int: - """ - Get the length of the contents of a `Message`. - - [Rust - `pactffi_message_get_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_contents_length) - - # Safety - - This function is safe. - - # Error Handling - - If the message is NULL, returns 0. If the body of the message is missing, - then this function also returns 0. - """ - raise NotImplementedError - - -def message_get_contents_bin(message: Message) -> str: - """ - Get the contents of a `Message` as a pointer to an array of bytes. - - [Rust - `pactffi_message_get_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_contents_bin) - - # Safety - - The number of bytes in the buffer will be returned by - `pactffi_message_get_contents_length`. It is safe to use the pointer while - the message is not deleted or changed. Using the pointer after the message - is mutated or deleted may lead to undefined behaviour. - - # Error Handling - - If the message is NULL, returns NULL. If the body of the message is missing, - then this function also returns NULL. - """ - raise NotImplementedError - - -def message_set_contents_bin( - message: Message, - contents: str, - len: int, - content_type: str, -) -> None: - """ - Sets the contents of the message as an array of bytes. - - [Rust - `pactffi_message_set_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_set_contents_bin) - - # Safety - - The contents pointer must be valid for reads of `len` bytes, and it must be - properly aligned and consecutive. Otherwise behaviour is undefined. - - # Error Handling - - If the contents is a NULL pointer, it will set the message contents as null. - If the content type is a null pointer, or can't be parsed, it will set the - content type as unknown. - """ - raise NotImplementedError - - -def message_get_description(message: Message) -> OwnedString: - r""" - Get a copy of the description. - - [Rust - `pactffi_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_description) - - # Safety - - The returned string must be deleted with `pactffi_string_delete`. - - Since it is a copy, the returned string may safely outlive the `Message`. - - # Errors - - On failure, this function will return a NULL pointer. - - This function may fail if the Rust string contains embedded null ('\0') - bytes. - """ - raise NotImplementedError - - -def message_set_description(message: Message, description: str) -> int: - """ - Write the `description` field on the `Message`. - - [Rust - `pactffi_message_set_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_set_description) - - # Safety - - `description` must contain valid UTF-8. Invalid UTF-8 will be replaced with - U+FFFD REPLACEMENT CHARACTER. - - This function will only reallocate if the new string does not fit in the - existing buffer. - - # Error Handling - - Errors will be reported with a non-zero return value. - """ - raise NotImplementedError - - -def message_get_provider_state(message: Message, index: int) -> ProviderState: - r""" - Get a copy of the provider state at the given index from this message. - - [Rust - `pactffi_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_provider_state) - - # Safety - - The returned structure must be deleted with `provider_state_delete`. - - Since it is a copy, the returned structure may safely outlive the `Message`. - - # Error Handling - - On failure, this function will return a variant other than Success. - - This function may fail if the index requested is out of bounds, or if any of - the Rust strings contain embedded null ('\0') bytes. - """ - raise NotImplementedError - - -def message_get_provider_state_iter(message: Message) -> ProviderStateIterator: - """ - Get an iterator over provider states. - - [Rust - `pactffi_message_get_provider_state_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_provider_state_iter) - - # Safety - - The underlying data must not change during iteration. - - # Error Handling - - Returns NULL if an error occurs. - """ - raise NotImplementedError - - def provider_state_iter_next(iter: ProviderStateIterator) -> ProviderState: """ Get the next value from the iterator. @@ -3947,52 +3563,6 @@ def provider_state_iter_delete(iter: ProviderStateIterator) -> None: raise NotImplementedError -def message_find_metadata(message: Message, key: str) -> str: - r""" - Get a copy of the metadata value indexed by `key`. - - [Rust - `pactffi_message_find_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_find_metadata) - - # Safety - - The returned string must be deleted with `pactffi_string_delete`. - - Since it is a copy, the returned string may safely outlive the `Message`. - - The returned pointer will be NULL if the metadata does not contain the given - key, or if an error occurred. - - # Error Handling - - On failure, this function will return a NULL pointer. - - This function may fail if the provided `key` string contains invalid UTF-8, - or if the Rust string contains embedded null ('\0') bytes. - """ - raise NotImplementedError - - -def message_insert_metadata(message: Message, key: str, value: str) -> int: - r""" - Insert the (`key`, `value`) pair into this Message's `metadata` HashMap. - - [Rust - `pactffi_message_insert_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_insert_metadata) - - # Safety - - This function returns an enum indicating the result; see the comments on - HashMapInsertStatus for details. - - # Error Handling - - This function may fail if the provided `key` or `value` strings contain - invalid UTF-8. - """ - raise NotImplementedError - - def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadataPair: """ Get the next key and value out of the iterator, if possible. @@ -4016,31 +3586,6 @@ def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadata raise NotImplementedError -def message_get_metadata_iter(message: Message) -> MessageMetadataIterator: - r""" - Get an iterator over the metadata of a message. - - [Rust - `pactffi_message_get_metadata_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_metadata_iter) - - # Safety - - This iterator carries a pointer to the message, and must not outlive the - message. - - The message metadata also must not be modified during iteration. If it is, - the old iterator must be deleted and a new iterator created. - - # 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. - """ - raise NotImplementedError - - def message_metadata_iter_delete(iter: MessageMetadataIterator) -> None: """ Free the metadata iterator when you're done using it. @@ -6408,27 +5953,6 @@ def add_text_comment(interaction: InteractionHandle, comment: str) -> None: raise RuntimeError(msg) -def pact_handle_get_message_iter(pact: PactHandle) -> PactMessageIterator: - r""" - Get an iterator over all the messages of the Pact. - - [Rust - `pactffi_pact_handle_get_message_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_message_iter) - - # 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. - """ - return PactMessageIterator(lib.pactffi_pact_handle_get_message_iter(pact._ref)) - - def pact_handle_get_async_message_iter(pact: PactHandle) -> PactAsyncMessageIterator: r""" Get an iterator over all the asynchronous messages of the Pact. From 816c9c77088ecfc2d7004959c52668fbcd456c69 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 09:40:57 +1000 Subject: [PATCH 09/23] chore(ffi): implement MessageContents Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 186 +++++++++++++++++++++++++++++++-------------- 1 file changed, 131 insertions(+), 55 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index dbb0e14a9..4dc0024ea 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -571,7 +571,87 @@ def matching_rule(self) -> MatchingRule: class MatchingRuleResult: ... -class MessageContents: ... +class MessageContents: + def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = True) -> None: + """ + Initialise a Message Contents. + + Args: + ptr: + CFFI data structure. + + owned: + Whether the message is owned by something else or not. This + determines whether the message should be freed when the Python + object is destroyed. + """ + if ffi.typeof(ptr).cname != "struct MessageContents *": + msg = ( + "ptr must be a struct MessageContents, got" f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + self._owned = owned + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "MessageContents" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"MessageContents({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the MessageContents. + """ + if not self._owned: + message_contents_delete(self) + + @property + def contents(self) -> str | bytes | None: + """ + Get the contents of the message. + """ + return message_contents_get_contents_str( + self + ) or message_contents_get_contents_bin(self) + + @property + def metadata(self) -> GeneratorType[MessageMetadataPair, None, None]: + """ + Get the metadata for the message contents. + """ + yield from message_contents_get_metadata_iter(self) + return # Ensures that the parent object outlives the generator + + def matching_rules( + self, + category: MatchingRuleCategoryOptions | MatchingRuleCategory, + ) -> GeneratorType[MatchingRuleKeyValuePair, None, None]: + """ + Get the matching rules for the message contents. + """ + if isinstance(category, str): + category = MatchingRuleCategory(category.upper()) + yield from message_contents_get_matching_rule_iter(self, category) + return # Ensures that the parent object outlives the generator + + def generators( + self, + category: GeneratorCategoryOptions | GeneratorCategory, + ) -> GeneratorType[GeneratorKeyValuePair, None, None]: + """ + Get the generators for the message contents. + """ + if isinstance(category, str): + category = GeneratorCategory(category.upper()) + yield from message_contents_get_generators_iter(self, category) + return # Ensures that the parent object outlives the generator class MessageMetadataIterator: ... @@ -2100,26 +2180,36 @@ def pact_consumer_delete(consumer: Consumer) -> None: raise NotImplementedError -def message_contents_get_contents_str(contents: MessageContents) -> str: +def message_contents_delete(contents: MessageContents) -> None: """ - Get the message contents in string form. + Delete the message contents instance. - [Rust `pactffi_message_contents_get_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_str) + This should only be called on a message contents that require deletion. + The function creating the message contents should document whether it + requires deletion. - # Safety + Deleting a message content which is associated with an interaction + will result in undefined behaviour. - The returned string must be deleted with `pactffi_string_delete`. + [Rust `pactffi_message_contents_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_delete) + """ + lib.pactffi_message_contents_delete(contents._ptr) - The returned string can outlive the message. - # Error Handling +def message_contents_get_contents_str(contents: MessageContents) -> str | None: + """ + Get the message contents in string form. - If the message contents is NULL, returns NULL. If the body of the message - is missing, then this function also returns NULL. This means there's - no mechanism to differentiate with this function call alone between - a NULL message and a missing message body. + [Rust `pactffi_message_contents_get_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_str) + + If the message has no contents or contain invalid UTF-8 characters, this + function will return `None`. + # Safety """ - raise NotImplementedError + ptr = lib.pactffi_message_contents_get_contents_str(contents._ptr) + if ptr == ffi.NULL: + return None + return OwnedString(ptr) def message_contents_set_contents_str( @@ -2160,38 +2250,27 @@ def message_contents_get_contents_length(contents: MessageContents) -> int: [Rust `pactffi_message_contents_get_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_length) - # Safety - - This function is safe. - - # Error Handling - - If the message is NULL, returns 0. If the body of the message - is missing, then this function also returns 0. + If the message has not contents, this function will return 0. """ - raise NotImplementedError + return lib.pactffi_message_contents_get_contents_length(contents._ptr) -def message_contents_get_contents_bin(contents: MessageContents) -> str: +def message_contents_get_contents_bin(contents: MessageContents) -> bytes | None: """ Get the contents of a message as a pointer to an array of bytes. [Rust `pactffi_message_contents_get_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_bin) - # Safety - - The number of bytes in the buffer will be returned by - `pactffi_message_contents_get_contents_length`. It is safe to use the - pointer while the message is not deleted or changed. Using the pointer after - the message is mutated or deleted may lead to undefined behaviour. - - # Error Handling - - If the message is NULL, returns NULL. If the body of the message is missing, - then this function also returns NULL. + If the message has no contents, this function will return `None`. """ - raise NotImplementedError + ptr = lib.pactffi_message_contents_get_contents_bin(contents._ptr) + if ptr == ffi.NULL: + return None + return ffi.buffer( + ptr, + lib.pactffi_message_contents_get_contents_length(contents._ptr), + )[:] def message_contents_set_contents_bin( @@ -2235,9 +2314,6 @@ def message_contents_get_metadata_iter( [Rust `pactffi_message_contents_get_metadata_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_metadata_iter) - The returned pointer must be deleted with - `pactffi_message_metadata_iter_delete` when done with it. - # Safety This iterator carries a pointer to the message contents, and must not @@ -2246,14 +2322,14 @@ def message_contents_get_metadata_iter( The message metadata also must not be modified during iteration. If it is, the old iterator must be deleted and a new iterator created. - # 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. + Raises: + RuntimeError: If the metadata iterator cannot be retrieved. """ - raise NotImplementedError + ptr = lib.pactffi_message_contents_get_metadata_iter(contents._ptr) + if ptr == ffi.NULL: + msg = "Unable to get the metadata iterator from the message contents." + raise RuntimeError(msg) + return MessageMetadataIterator(ptr) def message_contents_get_matching_rule_iter( @@ -2294,7 +2370,9 @@ def message_contents_get_matching_rule_iter( On failure, this function will return a NULL pointer. """ - raise NotImplementedError + return MatchingRuleCategoryIterator( + lib.pactffi_message_contents_get_matching_rule_iter(contents._ptr, category) + ) def request_contents_get_matching_rule_iter( @@ -2381,21 +2459,19 @@ def message_contents_get_generators_iter( [Rust `pactffi_message_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_generators_iter) - The returned pointer must be deleted with `pactffi_generators_iter_delete` - when done with it. - # Safety The iterator contains a copy of the data, so is safe to use when the message or message contents has been deleted. - # Error Handling - - On failure, this function will return a NULL pointer. + Raises: + RuntimeError: If the generators iterator cannot be retrieved. """ - return GeneratorCategoryIterator( - lib.pactffi_message_contents_get_generators_iter(contents, category) - ) + ptr = lib.pactffi_message_contents_get_generators_iter(contents._ptr, category) + if ptr == ffi.NULL: + msg = "Unable to get the generators iterator from the message contents." + raise RuntimeError(msg) + return GeneratorCategoryIterator(ptr) def request_contents_get_generators_iter( From e3855824fb47e18f95811fee403218a93c5d83db Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 09:45:32 +1000 Subject: [PATCH 10/23] chore(ffi): implement MessageMetadataPair and Iterator Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 124 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 8 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 4dc0024ea..4f2861387 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -654,10 +654,116 @@ def generators( return # Ensures that the parent object outlives the generator -class MessageMetadataIterator: ... +class MessageMetadataIterator: + """ + Iterator over an interaction's metadata. + """ + + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new Message Metadata Iterator. + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct MessageMetadataIterator *": + msg = ( + "ptr must be a struct MessageMetadataIterator, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr -class MessageMetadataPair: ... + def __str__(self) -> str: + """ + Nice string representation. + """ + return "MessageMetadataIterator" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"MessageMetadataIterator({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the Pact Interaction Iterator. + """ + message_metadata_iter_delete(self) + + def __iter__(self) -> Self: + """ + Return the iterator itself. + """ + return self + + def __next__(self) -> MessageMetadataPair: + """ + Get the next interaction from the iterator. + """ + return message_metadata_iter_next(self) + + +class MessageMetadataPair: + """ + A metadata key-value pair. + """ + + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new Message Metadata Pair. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct MessageMetadataPair *": + msg = ( + "ptr must be a struct MessageMetadataPair, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "MessageMetadataPair" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"MessageMetadataPair({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the Pact Interaction Iterator. + """ + message_metadata_pair_delete(self) + + @property + def key(self) -> str: + """ + Metadata key. + """ + s = ffi.string(self._ptr.key) # type: ignore[attr-defined] + if isinstance(s, bytes): + s = s.decode("utf-8") + return s + + @property + def value(self) -> str: + """ + Metadata value. + """ + s = ffi.string(self._ptr.value) # type: ignore[attr-defined] + if isinstance(s, bytes): + s = s.decode("utf-8") + return s class Mismatch: ... @@ -3655,11 +3761,13 @@ def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadata only ever be called from a foreign language. Calling it from a Rust function that has a Tokio runtime in its call stack can result in a deadlock. - # Error Handling - - If no further data is present, returns NULL. + Raises: + StopIteration: If no further data is present. """ - raise NotImplementedError + ptr = lib.pactffi_message_metadata_iter_next(iter._ptr) + if ptr == ffi.NULL: + raise StopIteration + return MessageMetadataPair(ptr) def message_metadata_iter_delete(iter: MessageMetadataIterator) -> None: @@ -3669,7 +3777,7 @@ def message_metadata_iter_delete(iter: MessageMetadataIterator) -> None: [Rust `pactffi_message_metadata_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_metadata_iter_delete) """ - raise NotImplementedError + lib.pactffi_message_metadata_iter_delete(iter._ptr) def message_metadata_pair_delete(pair: MessageMetadataPair) -> None: @@ -3679,7 +3787,7 @@ def message_metadata_pair_delete(pair: MessageMetadataPair) -> None: [Rust `pactffi_message_metadata_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_metadata_pair_delete) """ - raise NotImplementedError + lib.pactffi_message_metadata_pair_delete(pair._ptr) def provider_get_name(provider: Provider) -> str: From 64ca049f90cb6cf6d531540bc0a21f89cfae5bd3 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 09:52:55 +1000 Subject: [PATCH 11/23] chore(ffi): implement ProviderState and related Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 271 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 229 insertions(+), 42 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 4f2861387..ec6d2b75b 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -92,7 +92,7 @@ import typing import warnings from enum import Enum -from typing import TYPE_CHECKING, Any, List, Literal +from typing import TYPE_CHECKING, Any, List, Literal, Tuple from typing import Generator as GeneratorType from pact.v3._ffi import ffi, lib # type: ignore[import] @@ -1076,16 +1076,208 @@ def __next__(self) -> SynchronousMessage: class Provider: ... -class ProviderState: ... +class ProviderState: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new ProviderState. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct ProviderState *": + msg = "ptr must be a struct ProviderState, got" f" {ffi.typeof(ptr).cname}" + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "ProviderState({self.name!r})" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"ProviderState({self._ptr!r})" + + @property + def name(self) -> str: + """ + Provider State name. + """ + return provider_state_get_name(self) or "" + + def parameters(self) -> GeneratorType[Tuple[str, str], None, None]: + """ + Provider State parameters. + + This is a generator that yields key-value pairs. + """ + for p in provider_state_get_param_iter(self): + yield p.key, p.value + return # Ensures that the parent object outlives the generator + + +class ProviderStateIterator: + """ + Iterator over an interactions ProviderStates. + """ + + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new Provider State Iterator. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct ProviderStateIterator *": + msg = ( + "ptr must be a struct ProviderStateIterator, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "ProviderStateIterator" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"ProviderStateIterator({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the Provider State Iterator. + """ + provider_state_iter_delete(self) + + def __iter__(self) -> ProviderStateIterator: + """ + Return the iterator itself. + """ + return self + + def __next__(self) -> ProviderState: + """ + Get the next message from the iterator. + """ + return provider_state_iter_next(self) + + +class ProviderStateParamIterator: + """ + Iterator over a Provider States Parameters. + """ + + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new Provider State Param Iterator. + + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct ProviderStateParamIterator *": + msg = ( + "ptr must be a struct ProviderStateParamIterator, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "ProviderStateParamIterator" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"ProviderStateParamIterator({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the Provider State Param Iterator. + """ + provider_state_param_iter_delete(self) + def __iter__(self) -> ProviderStateParamIterator: + """ + Return the iterator itself. + """ + return self -class ProviderStateIterator: ... + def __next__(self) -> ProviderStateParamPair: + """ + Get the next message from the iterator. + """ + return provider_state_param_iter_next(self) -class ProviderStateParamIterator: ... +class ProviderStateParamPair: + def __init__(self, ptr: cffi.FFI.CData) -> None: + """ + Initialise a new ProviderStateParamPair. + Args: + ptr: + CFFI data structure. + """ + if ffi.typeof(ptr).cname != "struct ProviderStateParamPair *": + msg = ( + "ptr must be a struct ProviderStateParamPair, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr -class ProviderStateParamPair: ... + def __str__(self) -> str: + """ + Nice string representation. + """ + return "ProviderStateParamPair" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"ProviderStateParamPair({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the Provider State Param Pair. + """ + provider_state_param_pair_delete(self) + + @property + def key(self) -> str: + """ + Provider State Param key. + """ + s = ffi.string(self._ptr.key) # type: ignore[attr-defined] + if isinstance(s, bytes): + s = s.decode("utf-8") + return s + + @property + def value(self) -> str: + """ + Provider State Param value. + """ + s = ffi.string(self._ptr.value) # type: ignore[attr-defined] + if isinstance(s, bytes): + s = s.decode("utf-8") + return s class SynchronousHttp: ... @@ -3725,14 +3917,14 @@ def provider_state_iter_next(iter: ProviderStateIterator) -> ProviderState: The underlying data must not change during iteration. - If a previous call panicked, then the internal mutex will have been poisoned - and this function will return NULL. - - # Error Handling - - Returns NULL if an error occurs. + Raises: + StopIteration: If no further data is present, or if an internal error + occurs. """ - raise NotImplementedError + provider_state = lib.pactffi_provider_state_iter_next(iter._ptr) + if provider_state == ffi.NULL: + raise StopIteration + return ProviderState(provider_state) def provider_state_iter_delete(iter: ProviderStateIterator) -> None: @@ -3742,7 +3934,7 @@ def provider_state_iter_delete(iter: ProviderStateIterator) -> None: [Rust `pactffi_provider_state_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_iter_delete) """ - raise NotImplementedError + lib.pactffi_provider_state_iter_delete(iter._ptr) def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadataPair: @@ -3861,24 +4053,22 @@ def pact_provider_delete(provider: Provider) -> None: raise NotImplementedError -def provider_state_get_name(provider_state: ProviderState) -> str: +def provider_state_get_name(provider_state: ProviderState) -> str | None: """ Get the name of the provider state as a string. - This needs to be deleted with `pactffi_string_delete`. - [Rust `pactffi_provider_state_get_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_get_name) - # Safety - - This function is safe. - - # Error Handling - - If the provider_state param is NULL, this returns NULL. + Raises: + RuntimeError: + If the name could not be retrieved. """ - raise NotImplementedError + ptr = lib.pactffi_provider_state_get_name(provider_state._ptr) + if ptr == ffi.NULL: + msg = "Failed to get provider state name." + raise RuntimeError(msg) + return OwnedString(ptr) def provider_state_get_param_iter( @@ -3898,14 +4088,14 @@ def provider_state_get_param_iter( The provider state params also must not be modified during iteration. If it is, the old iterator must be deleted and a new iterator created. - # Errors - - On failure, this function will return a NULL pointer. - - This function may fail if any of the Rust strings contain embedded null - ('\0') bytes. + Raises: + RuntimeError: If the iterator could not be created. """ - raise NotImplementedError + ptr = lib.pactffi_provider_state_get_param_iter(provider_state._ptr) + if ptr == ffi.NULL: + msg = "Failed to get provider state param iterator." + raise RuntimeError(msg) + return ProviderStateParamIterator(ptr) def provider_state_param_iter_next( @@ -3917,20 +4107,17 @@ def provider_state_param_iter_next( [Rust `pactffi_provider_state_param_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_param_iter_next) - Returns a pointer to a heap allocated array of 2 elements, the pointer to - the key string on the heap, and the pointer to the value string on the heap. - # Safety The underlying data must not be modified during iteration. - The user needs to free both the contained strings and the array. - - # Error Handling - - Returns NULL if there's no further elements or the iterator is NULL. + Raises: + StopIteration: If no further data is present. """ - raise NotImplementedError + provider_state_param = lib.pactffi_provider_state_param_iter_next(iter._ptr) + if provider_state_param == ffi.NULL: + raise StopIteration + return ProviderStateParamPair(provider_state_param) def provider_state_delete(provider_state: ProviderState) -> None: @@ -3950,7 +4137,7 @@ def provider_state_param_iter_delete(iter: ProviderStateParamIterator) -> None: [Rust `pactffi_provider_state_param_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_param_iter_delete) """ - raise NotImplementedError + lib.pactffi_provider_state_param_iter_delete(iter._ptr) def provider_state_param_pair_delete(pair: ProviderStateParamPair) -> None: @@ -3960,7 +4147,7 @@ def provider_state_param_pair_delete(pair: ProviderStateParamPair) -> None: [Rust `pactffi_provider_state_param_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_param_pair_delete) """ - raise NotImplementedError + lib.pactffi_provider_state_param_pair_delete(pair._ptr) def sync_message_new() -> SynchronousMessage: From 8a72416f0ca1c4d9c94f11beb6bde2f3f657902f Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 10:25:27 +1000 Subject: [PATCH 12/23] chore(ffi): implement SynchronousHttp Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 223 ++++++++++++++++++++++++++------------------- 1 file changed, 128 insertions(+), 95 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index ec6d2b75b..0d780f7b8 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -1280,7 +1280,80 @@ def value(self) -> str: return s -class SynchronousHttp: ... +class SynchronousHttp: + def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None: + """ + Initialise a new Synchronous HTTP Interaction. + + Args: + ptr: + CFFI data structure. + + owned: + Whether the message is owned by something else or not. This + determines whether the message should be freed when the Python + object is destroyed. + """ + if ffi.typeof(ptr).cname != "struct SynchronousHttp *": + msg = ( + "ptr must be a struct SynchronousHttp, got" f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + self._owned = owned + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "SynchronousHttp" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"SynchronousHttp({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the SynchronousHttp. + """ + if not self._owned: + sync_http_delete(self) + + @property + def description(self) -> str: + """ + Description of this message interaction. + + This needs to be unique in the pact file. + """ + return sync_http_get_description(self) + + def provider_states(self) -> GeneratorType[ProviderState, None, None]: + """ + Optional provider state for the interaction. + """ + yield from sync_http_get_provider_state_iter(self) + return # Ensures that the parent object outlives the generator + + @property + def request_contents(self) -> str | bytes | None: + """ + The contents of the request. + """ + return sync_http_get_request_contents( + self + ) or sync_http_get_request_contents_bin(self) + + @property + def response_contents(self) -> str | bytes | None: + """ + The contents of the response. + """ + return sync_http_get_response_contents( + self + ) or sync_http_get_response_contents_bin(self) class SynchronousMessage: ... @@ -3288,7 +3361,7 @@ def sync_http_delete(interaction: SynchronousHttp) -> None: [Rust `pactffi_sync_http_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_delete) """ - raise NotImplementedError + lib.pactffi_sync_http_delete(interaction) def sync_http_get_request(interaction: SynchronousHttp) -> HttpRequest: @@ -3311,27 +3384,20 @@ def sync_http_get_request(interaction: SynchronousHttp) -> HttpRequest: raise NotImplementedError -def sync_http_get_request_contents(interaction: SynchronousHttp) -> str: +def sync_http_get_request_contents(interaction: SynchronousHttp) -> str | None: """ Get the request contents of a `SynchronousHttp` interaction in string form. [Rust `pactffi_sync_http_get_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request_contents) - # Safety - - The returned string must be deleted with `pactffi_string_delete`. - - The returned string can outlive the interaction. - - # Error Handling - - If the interaction is NULL, returns NULL. If the body of the request is - missing, then this function also returns NULL. This means there's no - mechanism to differentiate with this function call alone between a NULL body - and a missing body. + Note that this function will return `None` if either the body is missing or + is `null`. """ - raise NotImplementedError + ptr = lib.pactffi_sync_http_get_request_contents(interaction._ptr) + if ptr == ffi.NULL: + return None + return OwnedString(ptr) def sync_http_set_request_contents( @@ -3373,38 +3439,28 @@ def sync_http_get_request_contents_length(interaction: SynchronousHttp) -> int: [Rust `pactffi_sync_http_get_request_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request_contents_length) - # Safety - - This function is safe. - - # Error Handling - - If the interaction is NULL, returns 0. If the body of the request is - missing, then this function also returns 0. + This function will return 0 if the body is missing. """ - raise NotImplementedError + return lib.pactffi_sync_http_get_request_contents_length(interaction._ptr) -def sync_http_get_request_contents_bin(interaction: SynchronousHttp) -> bytes: +def sync_http_get_request_contents_bin(interaction: SynchronousHttp) -> bytes | None: """ Get the request contents of a `SynchronousHttp` interaction as bytes. [Rust `pactffi_sync_http_get_request_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request_contents_bin) - # Safety - - The number of bytes in the buffer will be returned by - `pactffi_sync_http_get_request_contents_length`. It is safe to use the - pointer while the interaction is not deleted or changed. Using the pointer - after the interaction is mutated or deleted may lead to undefined behaviour. - - # Error Handling - - If the interaction is NULL, returns NULL. If the body of the request is - missing, then this function also returns NULL. + Note that this function will return `None` if either the body is missing or + is `null`. """ - raise NotImplementedError + ptr = lib.pactffi_sync_http_get_request_contents_bin(interaction._ptr) + if ptr == ffi.NULL: + return None + return ffi.buffer( + ptr, + sync_http_get_request_contents_length(interaction), + )[:] def sync_http_set_request_contents_bin( @@ -3459,28 +3515,20 @@ def sync_http_get_response(interaction: SynchronousHttp) -> HttpResponse: raise NotImplementedError -def sync_http_get_response_contents(interaction: SynchronousHttp) -> str: +def sync_http_get_response_contents(interaction: SynchronousHttp) -> str | None: """ Get the response contents of a `SynchronousHttp` interaction in string form. [Rust `pactffi_sync_http_get_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response_contents) - # Safety - - The returned string must be deleted with `pactffi_string_delete`. - - The returned string can outlive the interaction. - - # Error Handling - - If the interaction is NULL, returns NULL. - - If the body of the response is missing, then this function also returns - NULL. This means there's no mechanism to differentiate with this function - call alone between a NULL body and a missing body. + Note that this function will return `None` if either the body is missing or + is `null`. """ - raise NotImplementedError + ptr = lib.pactffi_sync_http_get_response_contents(interaction._ptr) + if ptr == ffi.NULL: + return None + return OwnedString(ptr) def sync_http_set_response_contents( @@ -3522,38 +3570,28 @@ def sync_http_get_response_contents_length(interaction: SynchronousHttp) -> int: [Rust `pactffi_sync_http_get_response_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response_contents_length) - # Safety - - This function is safe. - - # Error Handling - - If the interaction is NULL or the index is not valid, returns 0. If the body - of the response is missing, then this function also returns 0. + This function will return 0 if the body is missing. """ - raise NotImplementedError + return lib.pactffi_sync_http_get_response_contents_length(interaction._ptr) -def sync_http_get_response_contents_bin(interaction: SynchronousHttp) -> bytes: +def sync_http_get_response_contents_bin(interaction: SynchronousHttp) -> bytes | None: """ Get the response contents of a `SynchronousHttp` interaction as bytes. [Rust `pactffi_sync_http_get_response_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response_contents_bin) - # Safety - - The number of bytes in the buffer will be returned by - `pactffi_sync_http_get_response_contents_length`. It is safe to use the - pointer while the interaction is not deleted or changed. Using the pointer - after the interaction is mutated or deleted may lead to undefined behaviour. - - # Error Handling - - If the interaction is NULL, returns NULL. If the body of the response is - missing, then this function also returns NULL. + Note that this function will return `None` if either the body is missing or + is `null`. """ - raise NotImplementedError + ptr = lib.pactffi_sync_http_get_response_contents_bin(interaction._ptr) + if ptr == ffi.NULL: + return None + return ffi.buffer( + ptr, + sync_http_get_response_contents_length(interaction), + )[:] def sync_http_set_response_contents_bin( @@ -3595,21 +3633,14 @@ def sync_http_get_description(interaction: SynchronousHttp) -> str: [Rust `pactffi_sync_http_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_description) - # Safety - - The returned string must be deleted with `pactffi_string_delete`. - - Since it is a copy, the returned string may safely outlive the - `SynchronousHttp` interaction. - - # Errors - - On failure, this function will return a NULL pointer. - - This function may fail if the Rust string contains embedded null ('\0') - bytes. + Raises: + RuntimeError: If the description cannot be retrieved """ - raise NotImplementedError + ptr = lib.pactffi_sync_http_get_description(interaction._ptr) + if ptr == ffi.NULL: + msg = "Failed to get description" + raise RuntimeError(msg) + return OwnedString(ptr) def sync_http_set_description(interaction: SynchronousHttp, description: str) -> int: @@ -3674,11 +3705,14 @@ def sync_http_get_provider_state_iter( The underlying data must not change during iteration. - # Error Handling - - Returns NULL if an error occurs. + Raises: + RuntimeError: If the iterator cannot be retrieved """ - raise NotImplementedError + ptr = lib.pactffi_sync_http_get_provider_state_iter(interaction._ptr) + if ptr == ffi.NULL: + msg = "Failed to get provider state iterator" + raise RuntimeError(msg) + return ProviderStateIterator(ptr) def pact_interaction_as_synchronous_http( @@ -3807,8 +3841,7 @@ def pact_sync_http_iter_next(iter: PactSyncHttpIterator) -> SynchronousHttp: ptr = lib.pactffi_pact_sync_http_iter_next(iter._ptr) if ptr == ffi.NULL: raise StopIteration - raise NotImplementedError - return SynchronousHttp(ptr) + return SynchronousHttp(ptr, owned=True) def pact_sync_http_iter_delete(iter: PactSyncHttpIterator) -> None: From ac0535dfd43cf948c98d7bade6ea9e88a246f851 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 11:18:23 +1000 Subject: [PATCH 13/23] chore(ffi): implement SynchronousMessage Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 162 +++++++++++++++++++++++++++++---------------- 1 file changed, 105 insertions(+), 57 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 0d780f7b8..2d91adc67 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -1356,7 +1356,81 @@ def response_contents(self) -> str | bytes | None: ) or sync_http_get_response_contents_bin(self) -class SynchronousMessage: ... +class SynchronousMessage: + def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None: + """ + Initialise a new Synchronous Message. + + Args: + ptr: + CFFI data structure. + + owned: + Whether the message is owned by something else or not. This + determines whether the message should be freed when the Python + object is destroyed. + """ + if ffi.typeof(ptr).cname != "struct SynchronousMessage *": + msg = ( + "ptr must be a struct SynchronousMessage, got" + f" {ffi.typeof(ptr).cname}" + ) + raise TypeError(msg) + self._ptr = ptr + self._owned = owned + + def __str__(self) -> str: + """ + Nice string representation. + """ + return "SynchronousMessage" + + def __repr__(self) -> str: + """ + Debugging representation. + """ + return f"SynchronousMessage({self._ptr!r})" + + def __del__(self) -> None: + """ + Destructor for the SynchronousMessage. + """ + if not self._owned: + sync_message_delete(self) + + @property + def description(self) -> str: + """ + Description of this message interaction. + + This needs to be unique in the pact file. + """ + return sync_message_get_description(self) + + def provider_states(self) -> GeneratorType[ProviderState, None, None]: + """ + Optional provider state for the interaction. + """ + yield from sync_message_get_provider_state_iter(self) + return # Ensures that the parent object outlives the generator + + @property + def request_contents(self) -> MessageContents: + """ + The contents of the message. + """ + return sync_message_generate_request_contents(self) + + @property + def response_contents(self) -> GeneratorType[MessageContents, None, None]: + """ + The contents of the responses. + """ + yield from ( + sync_message_generate_response_contents(self, i) + for i in range(sync_message_get_number_responses(self)) + ) + return # Ensures that the parent object outlives the generator class VerifierHandle: @@ -3817,8 +3891,7 @@ def pact_sync_message_iter_next(iter: PactSyncMessageIterator) -> SynchronousMes ptr = lib.pactffi_pact_sync_message_iter_next(iter._ptr) if ptr == ffi.NULL: raise StopIteration - raise NotImplementedError - return SynchronousMessage(ptr) + return SynchronousMessage(ptr, owned=True) def pact_sync_message_iter_delete(iter: PactSyncMessageIterator) -> None: @@ -4208,7 +4281,7 @@ def sync_message_delete(message: SynchronousMessage) -> None: [Rust `pactffi_sync_message_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_delete) """ - raise NotImplementedError + lib.pactffi_sync_message_delete(message._ptr) def sync_message_get_request_contents_str(message: SynchronousMessage) -> str: @@ -4372,21 +4445,12 @@ def sync_message_generate_request_contents( [Rust `pactffi_sync_message_generate_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_request_contents) - - # Safety - - The data pointed to by the pointer this function returns will be deleted - when the message is deleted. Trying to use if after the message is deleted - will result in undefined behaviour. - - # Error Handling - - If the message is NULL, returns NULL. """ - return MessageContents( - lib.pactffi_sync_message_generate_request_contents(message._ptr), - owned=False, - ) + ptr = lib.pactffi_sync_message_generate_request_contents(message._ptr) + if ptr == ffi.NULL: + msg = "Failed to generate request contents" + raise RuntimeError(msg) + return MessageContents(ptr, owned=False) def sync_message_get_number_responses(message: SynchronousMessage) -> int: @@ -4396,15 +4460,9 @@ def sync_message_get_number_responses(message: SynchronousMessage) -> int: [Rust `pactffi_sync_message_get_number_responses`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_number_responses) - # Safety - - The message pointer must point to a valid SynchronousMessage. - - # Error Handling - - If the message is NULL, returns 0. + If the message is null, this function will return 0. """ - raise NotImplementedError + return lib.pactffi_sync_message_get_number_responses(message._ptr) def sync_message_get_response_contents_str( @@ -4595,20 +4653,14 @@ def sync_message_generate_response_contents( [Rust `pactffi_sync_message_generate_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_response_contents) - # Safety - - The data pointed to by the pointer this function returns will be deleted - when the message is deleted. Trying to use if after the message is deleted - will result in undefined behaviour. - - # Error Handling - - If the message is NULL or the index is not valid, returns NULL. + Raises: + RuntimeError: If the response contents could not be generated. """ - return MessageContents( - lib.pactffi_sync_message_generate_response_contents(message._ptr, index), - owned=False, - ) + ptr = lib.pactffi_sync_message_generate_response_contents(message._ptr, index) + if ptr == ffi.NULL: + msg = "Failed to generate response contents." + raise RuntimeError(msg) + return MessageContents(ptr, owned=False) def sync_message_get_description(message: SynchronousMessage) -> str: @@ -4618,21 +4670,14 @@ def sync_message_get_description(message: SynchronousMessage) -> str: [Rust `pactffi_sync_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_description) - # Safety - - The returned string must be deleted with `pactffi_string_delete`. - - Since it is a copy, the returned string may safely outlive the - `SynchronousMessage`. - - # Errors - - On failure, this function will return a NULL pointer. - - This function may fail if the Rust string contains embedded null ('\0') - bytes. + Raises: + RuntimeError: If the description could not be retrieved """ - raise NotImplementedError + ptr = lib.pactffi_sync_message_get_description(message._ptr) + if ptr == ffi.NULL: + msg = "Failed to get description." + raise RuntimeError(msg) + return OwnedString(ptr) def sync_message_set_description(message: SynchronousMessage, description: str) -> int: @@ -4697,11 +4742,14 @@ def sync_message_get_provider_state_iter( The underlying data must not change during iteration. - # Error Handling - - Returns NULL if an error occurs. + Raises: + RuntimeError: If the iterator could not be created. """ - raise NotImplementedError + ptr = lib.pactffi_sync_message_get_provider_state_iter(message._ptr) + if ptr == ffi.NULL: + msg = "Failed to get provider state iterator." + raise RuntimeError(msg) + return ProviderStateIterator(ptr) def string_delete(string: OwnedString) -> None: From b33adb4506ad14e576db568d79a6cbf2864edb81 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 11:46:02 +1000 Subject: [PATCH 14/23] docs(ffi): properly document exceptions Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 379 +++++++++++++++++++++++++++++++++------------ 1 file changed, 279 insertions(+), 100 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index 2d91adc67..d0eeaee96 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -162,6 +162,10 @@ def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None: Whether the message is owned by something else or not. This determines whether the message should be freed when the Python object is destroyed. + + Raises: + TypeError: + If the `ptr` is not a `struct AsynchronousMessage`. """ if ffi.typeof(ptr).cname != "struct AsynchronousMessage *": msg = ( @@ -228,6 +232,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct Generator`. """ if ffi.typeof(ptr).cname != "struct Generator *": msg = "ptr must be a struct Generator, got" f" {ffi.typeof(ptr).cname}" @@ -297,6 +305,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct GeneratorCategoryIterator`. """ if ffi.typeof(ptr).cname != "struct GeneratorCategoryIterator *": msg = ( @@ -345,6 +357,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct GeneratorKeyValuePair`. """ if ffi.typeof(ptr).cname != "struct GeneratorKeyValuePair *": msg = ( @@ -435,6 +451,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct MatchingRule`. """ if ffi.typeof(ptr).cname != "struct MatchingRule *": msg = "ptr must be a struct MatchingRule, got" f" {ffi.typeof(ptr).cname}" @@ -469,6 +489,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct MatchingRuleCategoryIterator`. """ if ffi.typeof(ptr).cname != "struct MatchingRuleCategoryIterator *": msg = ( @@ -523,6 +547,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct MatchingRuleKeyValuePair`. """ if ffi.typeof(ptr).cname != "struct MatchingRuleKeyValuePair *": msg = ( @@ -584,6 +612,10 @@ def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = True) -> None: Whether the message is owned by something else or not. This determines whether the message should be freed when the Python object is destroyed. + + Raises: + TypeError: + If the `ptr` is not a `struct MessageContents`. """ if ffi.typeof(ptr).cname != "struct MessageContents *": msg = ( @@ -666,6 +698,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct MessageMetadataIterator`. """ if ffi.typeof(ptr).cname != "struct MessageMetadataIterator *": msg = ( @@ -718,6 +754,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct MessageMetadataPair`. """ if ffi.typeof(ptr).cname != "struct MessageMetadataPair *": msg = ( @@ -790,6 +830,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct PactAsyncMessageIterator`. """ if ffi.typeof(ptr).cname != "struct PactAsyncMessageIterator *": msg = ( @@ -935,6 +979,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct PactInteractionIterator`. """ if ffi.typeof(ptr).cname != "struct PactInteractionIterator *": msg = ( @@ -981,6 +1029,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct PactSyncHttpIterator`. """ if ffi.typeof(ptr).cname != "struct PactSyncHttpIterator *": msg = ( @@ -1033,6 +1085,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct PactSyncMessageIterator`. """ if ffi.typeof(ptr).cname != "struct PactSyncMessageIterator *": msg = ( @@ -1084,6 +1140,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct ProviderState`. """ if ffi.typeof(ptr).cname != "struct ProviderState *": msg = "ptr must be a struct ProviderState, got" f" {ffi.typeof(ptr).cname}" @@ -1132,6 +1192,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct ProviderStateIterator`. """ if ffi.typeof(ptr).cname != "struct ProviderStateIterator *": msg = ( @@ -1184,6 +1248,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct ProviderStateParamIterator`. """ if ffi.typeof(ptr).cname != "struct ProviderStateParamIterator *": msg = ( @@ -1232,6 +1300,10 @@ def __init__(self, ptr: cffi.FFI.CData) -> None: Args: ptr: CFFI data structure. + + Raises: + TypeError: + If the `ptr` is not a `struct ProviderStateParamPair`. """ if ffi.typeof(ptr).cname != "struct ProviderStateParamPair *": msg = ( @@ -1293,6 +1365,10 @@ def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None: Whether the message is owned by something else or not. This determines whether the message should be freed when the Python object is destroyed. + + Raises: + TypeError: + If the `ptr` is not a `struct SynchronousHttp`. """ if ffi.typeof(ptr).cname != "struct SynchronousHttp *": msg = ( @@ -1369,6 +1445,10 @@ def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None: Whether the message is owned by something else or not. This determines whether the message should be freed when the Python object is destroyed. + + Raises: + TypeError: + If the `ptr` is not a `struct SynchronousMessage`. """ if ffi.typeof(ptr).cname != "struct SynchronousMessage *": msg = ( @@ -1674,6 +1754,10 @@ def __init__(self, cdata: cffi.FFI.CData) -> None: Args: cdata: CFFI data structure. + + Raises: + TypeError: + If the `cdata` is not a `struct StringResult`. """ if ffi.typeof(cdata).cname != "struct StringResult": msg = f"cdata must be a struct StringResult, got {ffi.typeof(cdata).cname}" @@ -1722,7 +1806,12 @@ def raise_exception(self) -> None: Raise an exception with the text of the result. Raises: - RuntimeError: If the result is an error. + RuntimeError: + If the result is an error. + + Raises: + RuntimeError: + If the result is an error. """ if self.is_failed: raise RuntimeError(self.text) @@ -2015,7 +2104,8 @@ def get_error_message(length: int = 1024) -> str | None: message. Raises: - RuntimeError: If the error message could not be retrieved. + RuntimeError: + If the error message could not be retrieved. """ buffer = ffi.new("char[]", length) ret: int = lib.pactffi_get_error_message(buffer, length) @@ -2066,7 +2156,8 @@ def log_to_stderr(level_filter: LevelFilter | str = LevelFilter.ERROR) -> None: insensitive). Raises: - RuntimeError: If there was an error setting the logger. + RuntimeError: + If there was an error setting the logger. """ if isinstance(level_filter, str): level_filter = LevelFilter[level_filter.upper()] @@ -2096,6 +2187,10 @@ def log_to_buffer(level_filter: LevelFilter | str = LevelFilter.ERROR) -> None: Convenience function to direct all logging to a task local memory buffer. [Rust `pactffi_log_to_buffer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_log_to_buffer) + + Raises: + RuntimeError: + If there was an error setting the logger. """ if isinstance(level_filter, str): level_filter = LevelFilter[level_filter.upper()] @@ -2475,7 +2570,8 @@ def async_message_get_description(message: AsynchronousMessage) -> str: `pactffi_async_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_description) Raises: - RuntimeError: If the description cannot be retrieved. + RuntimeError: + If the description cannot be retrieved. """ ptr = lib.pactffi_async_message_get_description(message._ptr) if ptr == ffi.NULL: @@ -2518,19 +2614,9 @@ def async_message_get_provider_state( [Rust `pactffi_async_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_provider_state) - # Safety - - The returned structure must be deleted with `provider_state_delete`. - - Since it is a copy, the returned structure may safely outlive the - `AsynchronousMessage`. - - # Error Handling - - On failure, this function will return a variant other than Success. - - This function may fail if the index requested is out of bounds, or if any of - the Rust strings contain embedded null ('\0') bytes. + Raises: + RuntimeError: + If the provider state cannot be retrieved. """ ptr = lib.pactffi_async_message_get_provider_state(message._ptr, index) if ptr == ffi.NULL: @@ -2649,7 +2735,6 @@ def message_contents_get_contents_str(contents: MessageContents) -> str | None: If the message has no contents or contain invalid UTF-8 characters, this function will return `None`. - # Safety """ ptr = lib.pactffi_message_contents_get_contents_str(contents._ptr) if ptr == ffi.NULL: @@ -2768,7 +2853,8 @@ def message_contents_get_metadata_iter( the old iterator must be deleted and a new iterator created. Raises: - RuntimeError: If the metadata iterator cannot be retrieved. + RuntimeError: + If the metadata iterator cannot be retrieved. """ ptr = lib.pactffi_message_contents_get_metadata_iter(contents._ptr) if ptr == ffi.NULL: @@ -2910,7 +2996,8 @@ def message_contents_get_generators_iter( or message contents has been deleted. Raises: - RuntimeError: If the generators iterator cannot be retrieved. + RuntimeError: + If the generators iterator cannot be retrieved. """ ptr = lib.pactffi_message_contents_get_generators_iter(contents._ptr, category) if ptr == ffi.NULL: @@ -3273,23 +3360,15 @@ def validate_datetime(value: str, format: str) -> None: """ Validates the date/time value against the date/time format string. - Raises an error if the value is not a valid date/time for the format string. - - If the value is valid, this function will return a zero status code - (EXIT_SUCCESS). If the value is not valid, will return a value of 1 - (EXIT_FAILURE) and set the error message which can be retrieved with - `pactffi_get_error_message`. - [Rust `pactffi_validate_datetime`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_validate_datetime) - # Errors If the function receives a panic, it will return 2 and the message - associated with the panic can be retrieved with `pactffi_get_error_message`. - - # Safety + Raises: + ValueError: + If the value is not a valid date/time for the format string. - This function is safe as long as the value and format parameters point to - valid NULL-terminated strings. + RuntimeError: + For any other error. """ ret = lib.pactffi_validate_datetime(value.encode(), format.encode()) if ret == 0: @@ -3386,14 +3465,9 @@ def generators_iter_next(iter: GeneratorCategoryIterator) -> GeneratorKeyValuePa The returned pointer must be deleted with `pactffi_generator_iter_pair_delete`. - # Safety - - The underlying data is owned by the `GeneratorKeyValuePair`, so is always - safe to use. - - # Error Handling - - If no further data is present, returns NULL. + Raises: + StopIteration: + If the iterator has reached the end. """ ptr = lib.pactffi_generators_iter_next(iter._ptr) if ptr == ffi.NULL: @@ -3708,7 +3782,8 @@ def sync_http_get_description(interaction: SynchronousHttp) -> str: `pactffi_sync_http_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_description) Raises: - RuntimeError: If the description cannot be retrieved + RuntimeError: + If the description cannot be retrieved """ ptr = lib.pactffi_sync_http_get_description(interaction._ptr) if ptr == ffi.NULL: @@ -3780,7 +3855,8 @@ def sync_http_get_provider_state_iter( The underlying data must not change during iteration. Raises: - RuntimeError: If the iterator cannot be retrieved + RuntimeError: + If the iterator cannot be retrieved """ ptr = lib.pactffi_sync_http_get_provider_state_iter(interaction._ptr) if ptr == ffi.NULL: @@ -3864,6 +3940,10 @@ def pact_async_message_iter_next(iter: PactAsyncMessageIterator) -> Asynchronous [Rust `pactffi_pact_async_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_async_message_iter_next) + + Raises: + StopIteration: + If the iterator has reached the end. """ ptr = lib.pactffi_pact_async_message_iter_next(iter._ptr) if ptr == ffi.NULL: @@ -3887,6 +3967,10 @@ def pact_sync_message_iter_next(iter: PactSyncMessageIterator) -> SynchronousMes [Rust `pactffi_pact_sync_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_sync_message_iter_next) + + Raises: + StopIteration: + If the iterator has reached the end. """ ptr = lib.pactffi_pact_sync_message_iter_next(iter._ptr) if ptr == ffi.NULL: @@ -3910,6 +3994,10 @@ def pact_sync_http_iter_next(iter: PactSyncHttpIterator) -> SynchronousHttp: [Rust `pactffi_pact_sync_http_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_sync_http_iter_next) + + Raises: + StopIteration: + If the iterator has reached the end. """ ptr = lib.pactffi_pact_sync_http_iter_next(iter._ptr) if ptr == ffi.NULL: @@ -3933,6 +4021,10 @@ def pact_interaction_iter_next(iter: PactInteractionIterator) -> PactInteraction [Rust `pactffi_pact_interaction_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_iter_next) + + Raises: + StopIteration: + If the iterator has reached the end. """ ptr = lib.pactffi_pact_interaction_iter_next(iter._ptr) if ptr == ffi.NULL: @@ -4024,8 +4116,8 @@ def provider_state_iter_next(iter: ProviderStateIterator) -> ProviderState: The underlying data must not change during iteration. Raises: - StopIteration: If no further data is present, or if an internal error - occurs. + StopIteration: + If no further data is present, or if an internal error occurs. """ provider_state = lib.pactffi_provider_state_iter_next(iter._ptr) if provider_state == ffi.NULL: @@ -4060,7 +4152,8 @@ def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadata that has a Tokio runtime in its call stack can result in a deadlock. Raises: - StopIteration: If no further data is present. + StopIteration: + If no further data is present. """ ptr = lib.pactffi_message_metadata_iter_next(iter._ptr) if ptr == ffi.NULL: @@ -4195,7 +4288,8 @@ def provider_state_get_param_iter( is, the old iterator must be deleted and a new iterator created. Raises: - RuntimeError: If the iterator could not be created. + RuntimeError: + If the iterator could not be created. """ ptr = lib.pactffi_provider_state_get_param_iter(provider_state._ptr) if ptr == ffi.NULL: @@ -4218,7 +4312,8 @@ def provider_state_param_iter_next( The underlying data must not be modified during iteration. Raises: - StopIteration: If no further data is present. + StopIteration: + If no further data is present. """ provider_state_param = lib.pactffi_provider_state_param_iter_next(iter._ptr) if provider_state_param == ffi.NULL: @@ -4445,6 +4540,10 @@ def sync_message_generate_request_contents( [Rust `pactffi_sync_message_generate_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_request_contents) + + Raises: + RuntimeError: + If the request contents cannot be generated """ ptr = lib.pactffi_sync_message_generate_request_contents(message._ptr) if ptr == ffi.NULL: @@ -4654,7 +4753,8 @@ def sync_message_generate_response_contents( `pactffi_sync_message_generate_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_response_contents) Raises: - RuntimeError: If the response contents could not be generated. + RuntimeError: + If the response contents could not be generated. """ ptr = lib.pactffi_sync_message_generate_response_contents(message._ptr, index) if ptr == ffi.NULL: @@ -4671,7 +4771,8 @@ def sync_message_get_description(message: SynchronousMessage) -> str: `pactffi_sync_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_description) Raises: - RuntimeError: If the description could not be retrieved + RuntimeError: + If the description could not be retrieved """ ptr = lib.pactffi_sync_message_get_description(message._ptr) if ptr == ffi.NULL: @@ -4743,7 +4844,8 @@ def sync_message_get_provider_state_iter( The underlying data must not change during iteration. Raises: - RuntimeError: If the iterator could not be created. + RuntimeError: + If the iterator could not be created. """ ptr = lib.pactffi_sync_message_get_provider_state_iter(message._ptr) if ptr == ffi.NULL: @@ -4897,8 +4999,9 @@ def create_mock_server_for_transport( A handle to the mock server. Raises: - RuntimeError: If the mock server could not be created. The error message - will contain details of the error. + RuntimeError: + If the mock server could not be created. The error message will + contain details of the error. """ ret: int = lib.pactffi_create_mock_server_for_transport( pact._ref, @@ -4950,8 +5053,9 @@ def mock_server_mismatches( # Errors Raises: - RuntimeError: If there is no mock server with the provided port number, - or the function panics. + RuntimeError: + If there is no mock server with the provided port number, or the + function panics. """ ptr = lib.pactffi_mock_server_mismatches(mock_server_handle._ref) if ptr == ffi.NULL: @@ -4978,7 +5082,8 @@ def cleanup_mock_server(mock_server_handle: PactServerHandle) -> None: Handle to the mock server to cleanup. Raises: - RuntimeError: If the mock server could not be cleaned up. + RuntimeError: + If the mock server could not be cleaned up. """ success: bool = lib.pactffi_cleanup_mock_server(mock_server_handle._ref) if not success: @@ -5013,7 +5118,8 @@ def write_pact_file( pact file will be merged with any existing pact file. Raises: - RuntimeError: If there was an error writing the pact file. + RuntimeError: + If there was an error writing the pact file. """ ret: int = lib.pactffi_write_pact_file( mock_server_handle._ref, @@ -5053,7 +5159,8 @@ def mock_server_logs(mock_server_handle: PactServerHandle) -> str: `pactffi_mock_server_logs`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mock_server_logs) Raises: - RuntimeError: If the logs for the mock server can not be retrieved. + RuntimeError: + If the logs for the mock server can not be retrieved. """ ptr = lib.pactffi_mock_server_logs(mock_server_handle._ref) if ptr == ffi.NULL: @@ -5283,7 +5390,11 @@ def upon_receiving(interaction: InteractionHandle, description: str) -> None: The interaction description. It needs to be unique for each Pact. Raises: - RuntimeError: If the interaction description could not be set. + NotImplementedError: + This function has intentionally been left unimplemented. + + RuntimeError: + If the interaction description could not be set. """ # This function has intentionally been left unimplemented. The rationale is # to avoid code of the form: @@ -5318,7 +5429,8 @@ def given(interaction: InteractionHandle, description: str) -> None: The provider state description. It needs to be unique. Raises: - RuntimeError: If the provider state could not be specified. + RuntimeError: + If the provider state could not be specified. """ success: bool = lib.pactffi_given(interaction._ref, description.encode("utf-8")) if not success: @@ -5343,23 +5455,10 @@ def interaction_test_name(interaction: InteractionHandle, test_name: str) -> Non test_name: The test name to set. - # Safety - - The test name parameter must be a valid pointer to a NULL terminated string. - Raises: - RuntimeError: If the test name can not be set. - - # Error Handling - - If the test name can not be set, this will return a positive value. + RuntimeError: + If the test name can not be set. - * `1` - Function panicked. Error message will be available by calling - `pactffi_get_error_message`. - * `2` - Handle was not valid. - * `3` - Mock server was already started and the integration can not be - modified. - * `4` - Not a V4 interaction. """ ret: int = lib.pactffi_interaction_test_name( interaction._ref, @@ -5410,7 +5509,8 @@ def given_with_param( Parameter value as JSON. Raises: - RuntimeError: If the interaction state could not be updated. + RuntimeError: + If the interaction state could not be updated. """ success: bool = lib.pactffi_given_with_param( interaction._ref, @@ -5448,19 +5548,8 @@ def given_with_params( Parameter values as a JSON fragment. Raises: - RuntimeError: If the interaction state could not be updated. - - # Errors - - Returns EXIT_FAILURE (1) if the interaction or Pact can't be modified (i.e. - the mock server for it has already started). - - Returns 2 and sets the error message (which can be retrieved with - `pactffi_get_error_message`) if the parameter values con't be parsed as - JSON. - - Returns 3 if any of the C strings are not valid. - + RuntimeError: + If the interaction state could not be updated. """ ret: int = lib.pactffi_given_with_params( interaction._ref, @@ -5511,7 +5600,8 @@ def with_request(interaction: InteractionHandle, method: str, path: str) -> None ``` Raises: - RuntimeError: If the request could not be specified. + RuntimeError: + If the request could not be specified. """ success: bool = lib.pactffi_with_request( interaction._ref, @@ -5625,7 +5715,8 @@ def with_query_parameter_v2( rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). Raises: - RuntimeError: If there was an error setting the query parameter. + RuntimeError: + If there was an error setting the query parameter. """ success: bool = lib.pactffi_with_query_parameter_v2( interaction._ref, @@ -5651,6 +5742,10 @@ def with_specification(pact: PactHandle, version: PactSpecification) -> None: version: The spec version to use. + + Raises: + RuntimeError: + If the Pact specification could not be set. """ success: bool = lib.pactffi_with_specification(pact._ref, version.value) if not success: @@ -5703,6 +5798,10 @@ def with_pact_metadata( value: The value to set + + Raises: + RuntimeError: + If the metadata could not be set. """ success: bool = lib.pactffi_with_pact_metadata( pact._ref, @@ -5771,6 +5870,10 @@ def with_metadata( The key and value parameters must be valid pointers to NULL terminated strings, or `NULL` for the value parameter if the metadata key should be removed. + + Raises: + RuntimeError: + If the metadata could not be set. """ success: bool = lib.pactffi_with_metadata( interaction._ref, @@ -5856,7 +5959,8 @@ def with_header_v2( rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). Raises: - RuntimeError: If there was an error setting the header. + RuntimeError: + If there was an error setting the header. """ success: bool = lib.pactffi_with_header_v2( interaction._ref, @@ -5903,7 +6007,8 @@ def set_header( The header value. This is handled as-is, with no processing. Raises: - RuntimeError: If the header could not be set. + RuntimeError: + If the header could not be set. """ success: bool = lib.pactffi_set_header( interaction._ref, @@ -5931,7 +6036,8 @@ def response_status(interaction: InteractionHandle, status: int) -> None: The response status. Defaults to 200. Raises: - RuntimeError: If the response status could not be set. + RuntimeError: + If the response status could not be set. """ success: bool = lib.pactffi_response_status(interaction._ref, status) if not success: @@ -5977,7 +6083,8 @@ def response_status_v2(interaction: InteractionHandle, status: str) -> None: rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). Raises: - RuntimeError: If the response status could not be set. + RuntimeError: + If the response status could not be set. """ success: bool = lib.pactffi_response_status_v2( interaction._ref, status.encode("utf-8") @@ -6021,7 +6128,8 @@ def with_body( in the body. See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). Raises: - RuntimeError: If the body could not be specified. + RuntimeError: + If the body could not be specified. """ success: bool = lib.pactffi_with_body( interaction._ref, @@ -6068,7 +6176,8 @@ def with_binary_body( The body contents. If `None`, the body will be set to null. Raises: - RuntimeError: If the body could not be modified. + RuntimeError: + If the body could not be modified. """ raise NotImplementedError @@ -6108,6 +6217,10 @@ def with_binary_file( body: The body contents. If `None`, the body will be set to null. + + Raises: + RuntimeError: + If the body could not be set. """ if len(gc.get_referrers(body)) == 0: warnings.warn( @@ -6153,7 +6266,8 @@ def with_matching_rules( JSON string of the matching rules to add to the interaction. Raises: - RuntimeError: If the rules could not be added. + RuntimeError: + If the rules could not be added. """ success: bool = lib.pactffi_with_matching_rules( interaction._ref, @@ -6192,6 +6306,9 @@ def with_generators( generators: JSON string of the generators to add to the interaction. + Raises: + RuntimeError: + If the generators could not be added. """ success: bool = lib.pactffi_with_generators( interaction._ref, @@ -6319,6 +6436,10 @@ def set_key(interaction: InteractionHandle, key: str | None) -> None: key: Key value. This must be a valid UTF-8 null-terminated string, or `None` to clear the key. + + Raises: + RuntimeError: + If the key could not be set. """ success: bool = lib.pactffi_set_key( interaction._ref, @@ -6342,6 +6463,10 @@ def set_pending(interaction: InteractionHandle, *, pending: bool) -> None: pending: Boolean value to toggle the pending state of the interaction. + + Raises: + RuntimeError: + If the pending status could not be updated. """ success: bool = lib.pactffi_set_pending(interaction._ref, pending) if not success: @@ -6370,7 +6495,8 @@ def set_comment(interaction: InteractionHandle, key: str, value: str | None) -> null. Raises: - RuntimeError: If the comments could not be updated. + RuntimeError: + If the comments could not be updated. """ success: bool = lib.pactffi_set_comment( interaction._ref, @@ -6395,6 +6521,10 @@ def add_text_comment(interaction: InteractionHandle, comment: str) -> None: comment: Comment value. This is a regular string value. + + Raises: + RuntimeError: + If the comment could not be added. """ success: bool = lib.pactffi_add_text_comment( interaction._ref, @@ -6507,6 +6637,10 @@ def pact_handle_write_file( If `True`, the file will be overwritten with the contents of the current pact. Otherwise, it will be merged with any existing pact file. + + Raises: + RuntimeError: + If there was an error writing the pact file. """ ret: int = lib.pactffi_pact_handle_write_file( pact._ref, @@ -6534,7 +6668,8 @@ def free_pact_handle(pact: PactHandle) -> None: `pactffi_free_pact_handle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_free_pact_handle) Raises: - RuntimeError: If the handle could not be freed. + RuntimeError: + If the handle could not be freed. """ ret: int = lib.pactffi_free_pact_handle(pact._ref) if ret == 0: @@ -6778,6 +6913,10 @@ def verifier_set_verification_options( request_timeout: The timeout for the request in milliseconds. + + Raises: + RuntimeError: + If the options could not be set. """ retval: int = lib.pactffi_verifier_set_verification_options( handle._ref, @@ -6808,6 +6947,10 @@ def verifier_set_coloured_output( enabled: A boolean value to enable or disable coloured output. + + Raises: + RuntimeError: + If the coloured output could not be set. """ retval: int = lib.pactffi_verifier_set_coloured_output( handle._ref, @@ -6831,6 +6974,10 @@ def verifier_set_no_pacts_is_error(handle: VerifierHandle, *, enabled: bool) -> enabled: If `True`, an error will be raised when no pacts are found to verify. + + Raises: + RuntimeError: + If the no pacts is error setting could not be set. """ retval: int = lib.pactffi_verifier_set_no_pacts_is_error( handle._ref, @@ -6869,6 +7016,10 @@ def verifier_set_publish_options( provider_branch: Name of the branch used for verification. + + Raises: + RuntimeError: + If the publish options could not be set. """ retval: int = lib.pactffi_verifier_set_publish_options( handle._ref, @@ -7113,6 +7264,10 @@ def verifier_execute(handle: VerifierHandle) -> None: Runs the verification. (https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_execute) + + Raises: + RuntimeError: + If the verifier could not be executed. """ success: int = lib.pactffi_verifier_execute(handle._ref) if success != 0: @@ -7189,6 +7344,10 @@ def verifier_logs(handle: VerifierHandle) -> OwnedString: This needs the memory buffer log sink to be setup before the verification is executed. The returned string will need to be freed with the `free_string` function call to avoid leaking memory. + + Raises: + RuntimeError: + If the logs could not be extracted. """ ptr = lib.pactffi_verifier_logs(handle._ref) if ptr == ffi.NULL: @@ -7207,6 +7366,10 @@ def verifier_logs_for_provider(provider_name: str) -> OwnedString: This needs the memory buffer log sink to be setup before the verification is executed. The returned string will need to be freed with the `free_string` function call to avoid leaking memory. + + Raises: + RuntimeError: + If the logs could not be extracted. """ ptr = lib.pactffi_verifier_logs_for_provider(provider_name.encode("utf-8")) if ptr == ffi.NULL: @@ -7230,6 +7393,10 @@ def verifier_output(handle: VerifierHandle, strip_ansi: int) -> OwnedString: This parameter controls ANSI escape codes. Setting it to a non-zero value will cause the ANSI control codes to be stripped from the output. + + Raises: + RuntimeError: + If the output could not be extracted. """ ptr = lib.pactffi_verifier_output(handle._ref, strip_ansi) if ptr == ffi.NULL: @@ -7244,6 +7411,10 @@ def verifier_json(handle: VerifierHandle) -> OwnedString: [Rust `pactffi_verifier_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_json) + + Raises: + RuntimeError: + If the JSON could not be extracted. """ ptr = lib.pactffi_verifier_json(handle._ref) if ptr == ffi.NULL: @@ -7279,6 +7450,10 @@ def using_plugin( plugin_version: Version of the plugin to use. If `None`, the latest version will be used. + + Raises: + RuntimeError: + If the plugin could not be loaded. """ ret: int = lib.pactffi_using_plugin( pact._ref, @@ -7340,6 +7515,10 @@ def interaction_contents( contents: JSON contents that gets passed to the plugin. + + Raises: + RuntimeError: + If the interaction could not be configured """ ret: int = lib.pactffi_interaction_contents( interaction._ref, From 31b1143ea3de0d5a1f7618a52b4833b9baa17c02 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 11:47:36 +1000 Subject: [PATCH 15/23] chore(ffi): bump links to 0.4.21 Signed-off-by: JP-Ellis --- src/pact/v3/ffi.py | 490 +++++++++---------- src/pact/v3/interaction/_http_interaction.py | 4 +- 2 files changed, 247 insertions(+), 247 deletions(-) diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py index d0eeaee96..8108dcee4 100644 --- a/src/pact/v3/ffi.py +++ b/src/pact/v3/ffi.py @@ -417,7 +417,7 @@ class InteractionHandle: Handle to a HTTP Interaction. [Rust - `InteractionHandle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/handles/struct.InteractionHandle.html) + `InteractionHandle`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/handles/struct.InteractionHandle.html) """ def __init__(self, ref: int) -> None: @@ -879,7 +879,7 @@ class PactHandle: Handle to a Pact. [Rust - `PactHandle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/handles/struct.PactHandle.html) + `PactHandle`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/handles/struct.PactHandle.html) """ def __init__(self, ref: int) -> None: @@ -1517,7 +1517,7 @@ class VerifierHandle: """ Handle to a Verifier. - [Rust `VerifierHandle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/verifier/handle/struct.VerifierHandle.html) + [Rust `VerifierHandle`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/verifier/handle/struct.VerifierHandle.html) """ def __init__(self, ref: cffi.FFI.CData) -> None: @@ -1553,7 +1553,7 @@ class ExpressionValueType(Enum): """ Expression Value Type. - [Rust `ExpressionValueType`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/expressions/enum.ExpressionValueType.html) + [Rust `ExpressionValueType`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/expressions/enum.ExpressionValueType.html) """ UNKNOWN = lib.ExpressionValueType_Unknown @@ -1580,7 +1580,7 @@ class GeneratorCategory(Enum): """ Generator Category. - [Rust `GeneratorCategory`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/generators/enum.GeneratorCategory.html) + [Rust `GeneratorCategory`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/generators/enum.GeneratorCategory.html) """ METHOD = lib.GeneratorCategory_METHOD @@ -1608,7 +1608,7 @@ class InteractionPart(Enum): """ Interaction Part. - [Rust `InteractionPart`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/handles/enum.InteractionPart.html) + [Rust `InteractionPart`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/handles/enum.InteractionPart.html) """ REQUEST = lib.InteractionPart_Request @@ -1654,7 +1654,7 @@ class MatchingRuleCategory(Enum): """ Matching Rule Category. - [Rust `MatchingRuleCategory`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/matching_rules/enum.MatchingRuleCategory.html) + [Rust `MatchingRuleCategory`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/matching_rules/enum.MatchingRuleCategory.html) """ METHOD = lib.MatchingRuleCategory_METHOD @@ -1683,7 +1683,7 @@ class PactSpecification(Enum): """ Pact Specification. - [Rust `PactSpecification`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/pact_specification/enum.PactSpecification.html) + [Rust `PactSpecification`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/pact_specification/enum.PactSpecification.html) """ UNKNOWN = lib.PactSpecification_Unknown @@ -1736,7 +1736,7 @@ class _StringResult(Enum): """ Internal enum from Pact FFI. - [Rust `StringResult`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/enum.StringResult.html) + [Rust `StringResult`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/enum.StringResult.html) """ FAILED = lib.StringResult_Failed @@ -1892,7 +1892,7 @@ def version() -> str: """ Return the version of the pact_ffi library. - [Rust `pactffi_version`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_version) + [Rust `pactffi_version`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_version) Returns: The version of the pact_ffi library as a string, in the form of `x.y.z`. @@ -1912,7 +1912,7 @@ def init(log_env_var: str) -> None: tracing subscriber. [Rust - `pactffi_init`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_init) + `pactffi_init`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_init) # Safety @@ -1929,7 +1929,7 @@ def init_with_log_level(level: str = "INFO") -> None: tracing subscriber. [Rust - `pactffi_init_with_log_level`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_init_with_log_level) + `pactffi_init_with_log_level`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_init_with_log_level) Args: level: @@ -1949,7 +1949,7 @@ def enable_ansi_support() -> None: On non-Windows platforms, this function is a no-op. [Rust - `pactffi_enable_ansi_support`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_enable_ansi_support) + `pactffi_enable_ansi_support`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_enable_ansi_support) # Safety @@ -1967,7 +1967,7 @@ def log_message( Log using the shared core logging facility. [Rust - `pactffi_log_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_log_message) + `pactffi_log_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_message) This is useful for callers to have a single set of logs. @@ -1999,7 +1999,7 @@ def mismatches_get_iter(mismatches: Mismatches) -> MismatchesIterator: Get an iterator over mismatches. [Rust - `pactffi_mismatches_get_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatches_get_iter) + `pactffi_mismatches_get_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatches_get_iter) """ raise NotImplementedError @@ -2008,7 +2008,7 @@ def mismatches_delete(mismatches: Mismatches) -> None: """ Delete mismatches. - [Rust `pactffi_mismatches_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatches_delete) + [Rust `pactffi_mismatches_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatches_delete) """ raise NotImplementedError @@ -2017,7 +2017,7 @@ def mismatches_iter_next(iter: MismatchesIterator) -> Mismatch: """ Get the next mismatch from a mismatches iterator. - [Rust `pactffi_mismatches_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatches_iter_next) + [Rust `pactffi_mismatches_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatches_iter_next) Returns a null pointer if no mismatches remain. """ @@ -2028,7 +2028,7 @@ def mismatches_iter_delete(iter: MismatchesIterator) -> None: """ Delete a mismatches iterator when you're done with it. - [Rust `pactffi_mismatches_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatches_iter_delete) + [Rust `pactffi_mismatches_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatches_iter_delete) """ raise NotImplementedError @@ -2037,7 +2037,7 @@ def mismatch_to_json(mismatch: Mismatch) -> str: """ Get a JSON representation of the mismatch. - [Rust `pactffi_mismatch_to_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatch_to_json) + [Rust `pactffi_mismatch_to_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_to_json) """ raise NotImplementedError @@ -2046,7 +2046,7 @@ def mismatch_type(mismatch: Mismatch) -> str: """ Get the type of a mismatch. - [Rust `pactffi_mismatch_type`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatch_type) + [Rust `pactffi_mismatch_type`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_type) """ raise NotImplementedError @@ -2055,7 +2055,7 @@ def mismatch_summary(mismatch: Mismatch) -> str: """ Get a summary of a mismatch. - [Rust `pactffi_mismatch_summary`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatch_summary) + [Rust `pactffi_mismatch_summary`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_summary) """ raise NotImplementedError @@ -2064,7 +2064,7 @@ def mismatch_description(mismatch: Mismatch) -> str: """ Get a description of a mismatch. - [Rust `pactffi_mismatch_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatch_description) + [Rust `pactffi_mismatch_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_description) """ raise NotImplementedError @@ -2073,7 +2073,7 @@ def mismatch_ansi_description(mismatch: Mismatch) -> str: """ Get an ANSI-compatible description of a mismatch. - [Rust `pactffi_mismatch_ansi_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatch_ansi_description) + [Rust `pactffi_mismatch_ansi_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_ansi_description) """ raise NotImplementedError @@ -2083,7 +2083,7 @@ def get_error_message(length: int = 1024) -> str | None: Provide the error message from `LAST_ERROR` to the calling C code. [Rust - `pactffi_get_error_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_get_error_message) + `pactffi_get_error_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_get_error_message) This function should be called after any other function in the pact_matching FFI indicates a failure with its own error message, if the caller wants to @@ -2137,7 +2137,7 @@ def log_to_stdout(level_filter: LevelFilter) -> int: """ Convenience function to direct all logging to stdout. - [Rust `pactffi_log_to_stdout`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_log_to_stdout) + [Rust `pactffi_log_to_stdout`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_stdout) """ raise NotImplementedError @@ -2147,7 +2147,7 @@ def log_to_stderr(level_filter: LevelFilter | str = LevelFilter.ERROR) -> None: Convenience function to direct all logging to stderr. [Rust - `pactffi_log_to_stderr`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_log_to_stderr) + `pactffi_log_to_stderr`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_stderr) Args: level_filter: @@ -2172,7 +2172,7 @@ def log_to_file(file_name: str, level_filter: LevelFilter) -> int: Convenience function to direct all logging to a file. [Rust - `pactffi_log_to_file`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_log_to_file) + `pactffi_log_to_file`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_file) # Safety @@ -2186,7 +2186,7 @@ def log_to_buffer(level_filter: LevelFilter | str = LevelFilter.ERROR) -> None: """ Convenience function to direct all logging to a task local memory buffer. - [Rust `pactffi_log_to_buffer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_log_to_buffer) + [Rust `pactffi_log_to_buffer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_buffer) Raises: RuntimeError: @@ -2204,7 +2204,7 @@ def logger_init() -> None: """ Initialize the FFI logger with no sinks. - [Rust `pactffi_logger_init`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_logger_init) + [Rust `pactffi_logger_init`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_logger_init) This initialized logger does nothing until `pactffi_logger_apply` has been called. @@ -2226,7 +2226,7 @@ def logger_attach_sink(sink_specifier: str, level_filter: LevelFilter) -> int: Attach an additional sink to the thread-local logger. [Rust - `pactffi_logger_attach_sink`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_logger_attach_sink) + `pactffi_logger_attach_sink`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_logger_attach_sink) This logger does nothing until `pactffi_logger_apply` has been called. @@ -2273,7 +2273,7 @@ def logger_apply() -> int: Apply the previously configured sinks and levels to the program. [Rust - `pactffi_logger_apply`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_logger_apply) + `pactffi_logger_apply`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_logger_apply) If no sinks have been setup, will set the log level to info and the target to standard out. @@ -2289,7 +2289,7 @@ def fetch_log_buffer(log_id: str) -> str: Fetch the in-memory logger buffer contents. [Rust - `pactffi_fetch_log_buffer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_fetch_log_buffer) + `pactffi_fetch_log_buffer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_fetch_log_buffer) This will only have any contents if the `buffer` sink has been configured to log to. The contents will be allocated on the heap and will need to be freed @@ -2315,7 +2315,7 @@ def parse_pact_json(json: str) -> Pact: Parses the provided JSON into a Pact model. [Rust - `pactffi_parse_pact_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_parse_pact_json) + `pactffi_parse_pact_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_parse_pact_json) The returned Pact model must be freed with the `pactffi_pact_model_delete` function when no longer needed. @@ -2332,7 +2332,7 @@ def pact_model_delete(pact: Pact) -> None: """ Frees the memory used by the Pact model. - [Rust `pactffi_pact_model_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_model_delete) + [Rust `pactffi_pact_model_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_model_delete) """ raise NotImplementedError @@ -2342,7 +2342,7 @@ def pact_model_interaction_iterator(pact: Pact) -> PactInteractionIterator: Returns an iterator over all the interactions in the Pact. [Rust - `pactffi_pact_model_interaction_iterator`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_model_interaction_iterator) + `pactffi_pact_model_interaction_iterator`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_model_interaction_iterator) The iterator will have to be deleted using the `pactffi_pact_interaction_iter_delete` function. The iterator will contain a @@ -2361,7 +2361,7 @@ def pact_spec_version(pact: Pact) -> PactSpecification: """ Returns the Pact specification enum that the Pact is for. - [Rust `pactffi_pact_spec_version`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_spec_version) + [Rust `pactffi_pact_spec_version`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_spec_version) """ raise NotImplementedError @@ -2370,7 +2370,7 @@ def pact_interaction_delete(interaction: PactInteraction) -> None: """ Frees the memory used by the Pact interaction model. - [Rust `pactffi_pact_interaction_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_delete) + [Rust `pactffi_pact_interaction_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_delete) """ raise NotImplementedError @@ -2379,7 +2379,7 @@ def async_message_new() -> AsynchronousMessage: """ Get a mutable pointer to a newly-created default message on the heap. - [Rust `pactffi_async_message_new`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_new) + [Rust `pactffi_async_message_new`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_new) # Safety @@ -2396,7 +2396,7 @@ def async_message_delete(message: AsynchronousMessage) -> None: """ Destroy the `AsynchronousMessage` being pointed to. - [Rust `pactffi_async_message_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_delete) + [Rust `pactffi_async_message_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_delete) """ lib.pactffi_async_message_delete(message._ptr) @@ -2406,7 +2406,7 @@ def async_message_get_contents(message: AsynchronousMessage) -> MessageContents Get the message contents of an `AsynchronousMessage` as a `MessageContents` pointer. [Rust - `pactffi_async_message_get_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_contents) + `pactffi_async_message_get_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents) If the message contents are missing, this function will return `None`. """ @@ -2425,7 +2425,7 @@ def async_message_generate_contents( contents as would be received by the consumer. [Rust - `pactffi_async_message_generate_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_generate_contents) + `pactffi_async_message_generate_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_generate_contents) If the message contents are missing, this function will return `None`. """ @@ -2439,7 +2439,7 @@ def async_message_get_contents_str(message: AsynchronousMessage) -> str: """ Get the message contents of an `AsynchronousMessage` in string form. - [Rust `pactffi_async_message_get_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_contents_str) + [Rust `pactffi_async_message_get_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents_str) # Safety @@ -2466,7 +2466,7 @@ def async_message_set_contents_str( Sets the contents of the message as a string. [Rust - `pactffi_async_message_set_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_set_contents_str) + `pactffi_async_message_set_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_set_contents_str) - `message` - the message to set the contents for - `contents` - pointer to contents to copy from. Must be a valid @@ -2494,7 +2494,7 @@ def async_message_get_contents_length(message: AsynchronousMessage) -> int: Get the length of the contents of a `AsynchronousMessage`. [Rust - `pactffi_async_message_get_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_contents_length) + `pactffi_async_message_get_contents_length`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents_length) # Safety @@ -2513,7 +2513,7 @@ def async_message_get_contents_bin(message: AsynchronousMessage) -> str: Get the contents of an `AsynchronousMessage` as bytes. [Rust - `pactffi_async_message_get_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_contents_bin) + `pactffi_async_message_get_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents_bin) # Safety @@ -2540,7 +2540,7 @@ def async_message_set_contents_bin( Sets the contents of the message as an array of bytes. [Rust - `pactffi_async_message_set_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_set_contents_bin) + `pactffi_async_message_set_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_set_contents_bin) * `message` - the message to set the contents for * `contents` - pointer to contents to copy from @@ -2567,7 +2567,7 @@ def async_message_get_description(message: AsynchronousMessage) -> str: Get a copy of the description. [Rust - `pactffi_async_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_description) + `pactffi_async_message_get_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_description) Raises: RuntimeError: @@ -2587,7 +2587,7 @@ def async_message_set_description( """ Write the `description` field on the `AsynchronousMessage`. - [Rust `pactffi_async_message_set_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_set_description) + [Rust `pactffi_async_message_set_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_set_description) # Safety @@ -2612,7 +2612,7 @@ def async_message_get_provider_state( Get a copy of the provider state at the given index from this message. [Rust - `pactffi_async_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_provider_state) + `pactffi_async_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_provider_state) Raises: RuntimeError: @@ -2631,7 +2631,7 @@ def async_message_get_provider_state_iter( """ Get an iterator over provider states. - [Rust `pactffi_async_message_get_provider_state_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_provider_state_iter) + [Rust `pactffi_async_message_get_provider_state_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_provider_state_iter) # Safety @@ -2646,7 +2646,7 @@ def consumer_get_name(consumer: Consumer) -> str: r""" Get a copy of this consumer's name. - [Rust `pactffi_consumer_get_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_consumer_get_name) + [Rust `pactffi_consumer_get_name`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_consumer_get_name) The copy must be deleted with `pactffi_string_delete`. @@ -2692,7 +2692,7 @@ def pact_get_consumer(pact: Pact) -> Consumer: `pactffi_pact_consumer_delete` when no longer required. [Rust - `pactffi_pact_get_consumer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_get_consumer) + `pactffi_pact_get_consumer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_get_consumer) # Errors @@ -2706,7 +2706,7 @@ def pact_consumer_delete(consumer: Consumer) -> None: """ Frees the memory used by the Pact consumer. - [Rust `pactffi_pact_consumer_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_consumer_delete) + [Rust `pactffi_pact_consumer_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_consumer_delete) """ raise NotImplementedError @@ -2722,7 +2722,7 @@ def message_contents_delete(contents: MessageContents) -> None: Deleting a message content which is associated with an interaction will result in undefined behaviour. - [Rust `pactffi_message_contents_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_delete) + [Rust `pactffi_message_contents_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_delete) """ lib.pactffi_message_contents_delete(contents._ptr) @@ -2731,7 +2731,7 @@ def message_contents_get_contents_str(contents: MessageContents) -> str | None: """ Get the message contents in string form. - [Rust `pactffi_message_contents_get_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_str) + [Rust `pactffi_message_contents_get_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_contents_str) If the message has no contents or contain invalid UTF-8 characters, this function will return `None`. @@ -2751,7 +2751,7 @@ def message_contents_set_contents_str( Sets the contents of the message as a string. [Rust - `pactffi_message_contents_set_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_set_contents_str) + `pactffi_message_contents_set_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_set_contents_str) * `contents` - the message contents to set the contents for * `contents_str` - pointer to contents to copy from. Must be a valid @@ -2778,7 +2778,7 @@ def message_contents_get_contents_length(contents: MessageContents) -> int: """ Get the length of the message contents. - [Rust `pactffi_message_contents_get_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_length) + [Rust `pactffi_message_contents_get_contents_length`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_contents_length) If the message has not contents, this function will return 0. """ @@ -2790,7 +2790,7 @@ def message_contents_get_contents_bin(contents: MessageContents) -> bytes | None Get the contents of a message as a pointer to an array of bytes. [Rust - `pactffi_message_contents_get_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_bin) + `pactffi_message_contents_get_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_contents_bin) If the message has no contents, this function will return `None`. """ @@ -2813,7 +2813,7 @@ def message_contents_set_contents_bin( Sets the contents of the message as an array of bytes. [Rust - `pactffi_message_contents_set_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_set_contents_bin) + `pactffi_message_contents_set_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_set_contents_bin) * `message` - the message contents to set the contents for * `contents_bin` - pointer to contents to copy from @@ -2842,7 +2842,7 @@ def message_contents_get_metadata_iter( Get an iterator over the metadata of a message. [Rust - `pactffi_message_contents_get_metadata_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_metadata_iter) + `pactffi_message_contents_get_metadata_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_metadata_iter) # Safety @@ -2871,7 +2871,7 @@ def message_contents_get_matching_rule_iter( Get an iterator over the matching rules for a category of a message. [Rust - `pactffi_message_contents_get_matching_rule_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_matching_rule_iter) + `pactffi_message_contents_get_matching_rule_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_matching_rule_iter) The returned pointer must be deleted with `pactffi_matching_rules_iter_delete` when done with it. @@ -2913,7 +2913,7 @@ def request_contents_get_matching_rule_iter( r""" Get an iterator over the matching rules for a category of an HTTP request. - [Rust `pactffi_request_contents_get_matching_rule_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_request_contents_get_matching_rule_iter) + [Rust `pactffi_request_contents_get_matching_rule_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_request_contents_get_matching_rule_iter) The returned pointer must be deleted with `pactffi_matching_rules_iter_delete` when done with it. @@ -2950,7 +2950,7 @@ def response_contents_get_matching_rule_iter( r""" Get an iterator over the matching rules for a category of an HTTP response. - [Rust `pactffi_response_contents_get_matching_rule_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_response_contents_get_matching_rule_iter) + [Rust `pactffi_response_contents_get_matching_rule_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_response_contents_get_matching_rule_iter) The returned pointer must be deleted with `pactffi_matching_rules_iter_delete` when done with it. @@ -2988,7 +2988,7 @@ def message_contents_get_generators_iter( Get an iterator over the generators for a category of a message. [Rust - `pactffi_message_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_generators_iter) + `pactffi_message_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_generators_iter) # Safety @@ -3014,7 +3014,7 @@ def request_contents_get_generators_iter( Get an iterator over the generators for a category of an HTTP request. [Rust - `pactffi_request_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_request_contents_get_generators_iter) + `pactffi_request_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_request_contents_get_generators_iter) The returned pointer must be deleted with `pactffi_generators_iter_delete` when done with it. @@ -3039,7 +3039,7 @@ def response_contents_get_generators_iter( Get an iterator over the generators for a category of an HTTP response. [Rust - `pactffi_response_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_response_contents_get_generators_iter) + `pactffi_response_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_response_contents_get_generators_iter) The returned pointer must be deleted with `pactffi_generators_iter_delete` when done with it. @@ -3064,7 +3064,7 @@ def parse_matcher_definition(expression: str) -> MatchingRuleDefinitionResult: any generator. [Rust - `pactffi_parse_matcher_definition`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_parse_matcher_definition) + `pactffi_parse_matcher_definition`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_parse_matcher_definition) The following are examples of matching rule definitions: @@ -3100,7 +3100,7 @@ def matcher_definition_error(definition: MatchingRuleDefinitionResult) -> str: using the `pactffi_string_delete` function once done with it. [Rust - `pactffi_matcher_definition_error`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_error) + `pactffi_matcher_definition_error`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_error) """ raise NotImplementedError @@ -3114,7 +3114,7 @@ def matcher_definition_value(definition: MatchingRuleDefinitionResult) -> str: the `pactffi_string_delete` function once done with it. [Rust - `pactffi_matcher_definition_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_value) + `pactffi_matcher_definition_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_value) Note that different expressions values can have types other than a string. Use `pactffi_matcher_definition_value_type` to get the actual type of the @@ -3128,7 +3128,7 @@ def matcher_definition_delete(definition: MatchingRuleDefinitionResult) -> None: """ Frees the memory used by the result of parsing the matching definition expression. - [Rust `pactffi_matcher_definition_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_delete) + [Rust `pactffi_matcher_definition_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_delete) """ raise NotImplementedError @@ -3141,7 +3141,7 @@ def matcher_definition_generator(definition: MatchingRuleDefinitionResult) -> Ge NULL pointer, otherwise returns the generator as a pointer. [Rust - `pactffi_matcher_definition_generator`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_generator) + `pactffi_matcher_definition_generator`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_generator) The generator pointer will be a valid pointer as long as `pactffi_matcher_definition_delete` has not been called on the definition. @@ -3160,7 +3160,7 @@ def matcher_definition_value_type( If there was an error parsing the expression, it will return Unknown. [Rust - `pactffi_matcher_definition_value_type`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_value_type) + `pactffi_matcher_definition_value_type`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_value_type) """ raise NotImplementedError @@ -3169,7 +3169,7 @@ def matching_rule_iter_delete(iter: MatchingRuleIterator) -> None: """ Free the iterator when you're done using it. - [Rust `pactffi_matching_rule_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_iter_delete) + [Rust `pactffi_matching_rule_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_iter_delete) """ raise NotImplementedError @@ -3184,7 +3184,7 @@ def matcher_definition_iter( `pactffi_matching_rule_iter_delete` function once done with it. [Rust - `pactffi_matcher_definition_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_iter) + `pactffi_matcher_definition_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_iter) If there was an error parsing the expression, this function will return a NULL pointer. @@ -3200,7 +3200,7 @@ def matching_rule_iter_next(iter: MatchingRuleIterator) -> MatchingRuleResult: deleted but will be cleaned up when the iterator is deleted. [Rust - `pactffi_matching_rule_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_iter_next) + `pactffi_matching_rule_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_iter_next) Will return a NULL pointer when the iterator has advanced past the end of the list. @@ -3222,7 +3222,7 @@ def matching_rule_id(rule_result: MatchingRuleResult) -> int: Return the ID of the matching rule. [Rust - `pactffi_matching_rule_id`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_id) + `pactffi_matching_rule_id`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_id) The ID corresponds to the following rules: @@ -3268,7 +3268,7 @@ def matching_rule_value(rule_result: MatchingRuleResult) -> str: pointer. [Rust - `pactffi_matching_rule_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_value) + `pactffi_matching_rule_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_value) The associated values for the rules are: @@ -3316,7 +3316,7 @@ def matching_rule_pointer(rule_result: MatchingRuleResult) -> MatchingRule: Will return a NULL pointer if the matching rule result was a reference. [Rust - `pactffi_matching_rule_pointer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_pointer) + `pactffi_matching_rule_pointer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_pointer) # Safety @@ -3334,7 +3334,7 @@ def matching_rule_reference_name(rule_result: MatchingRuleResult) -> str: structure. I.e., [Rust - `pactffi_matching_rule_reference_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_reference_name) + `pactffi_matching_rule_reference_name`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_reference_name) ```json { @@ -3361,7 +3361,7 @@ def validate_datetime(value: str, format: str) -> None: Validates the date/time value against the date/time format string. [Rust - `pactffi_validate_datetime`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_validate_datetime) + `pactffi_validate_datetime`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_validate_datetime) Raises: ValueError: @@ -3388,7 +3388,7 @@ def generator_to_json(generator: Generator) -> str: Get the JSON form of the generator. [Rust - `pactffi_generator_to_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generator_to_json) + `pactffi_generator_to_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generator_to_json) The returned string must be deleted with `pactffi_string_delete`. @@ -3411,7 +3411,7 @@ def generator_generate_string(generator: Generator, context_json: str) -> str: function). [Rust - `pactffi_generator_generate_string`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generator_generate_string) + `pactffi_generator_generate_string`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generator_generate_string) If anything goes wrong, it will return a NULL pointer. """ @@ -3434,7 +3434,7 @@ def generator_generate_integer(generator: Generator, context_json: str) -> int: should be the values returned from the Provider State callback function). [Rust - `pactffi_generator_generate_integer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generator_generate_integer) + `pactffi_generator_generate_integer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generator_generate_integer) If anything goes wrong or the generator is not a type that can generate an integer value, it will return a zero value. @@ -3450,7 +3450,7 @@ def generators_iter_delete(iter: GeneratorCategoryIterator) -> None: Free the iterator when you're done using it. [Rust - `pactffi_generators_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generators_iter_delete) + `pactffi_generators_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generators_iter_delete) """ lib.pactffi_generators_iter_delete(iter._ptr) @@ -3460,7 +3460,7 @@ def generators_iter_next(iter: GeneratorCategoryIterator) -> GeneratorKeyValuePa Get the next path and generator out of the iterator, if possible. [Rust - `pactffi_generators_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generators_iter_next) + `pactffi_generators_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generators_iter_next) The returned pointer must be deleted with `pactffi_generator_iter_pair_delete`. @@ -3480,7 +3480,7 @@ def generators_iter_pair_delete(pair: GeneratorKeyValuePair) -> None: Free a pair of key and value returned from `pactffi_generators_iter_next`. [Rust - `pactffi_generators_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generators_iter_pair_delete) + `pactffi_generators_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generators_iter_pair_delete) """ lib.pactffi_generators_iter_pair_delete(pair._ptr) @@ -3489,7 +3489,7 @@ def sync_http_new() -> SynchronousHttp: """ Get a mutable pointer to a newly-created default interaction on the heap. - [Rust `pactffi_sync_http_new`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_new) + [Rust `pactffi_sync_http_new`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_new) # Safety @@ -3507,7 +3507,7 @@ def sync_http_delete(interaction: SynchronousHttp) -> None: Destroy the `SynchronousHttp` interaction being pointed to. [Rust - `pactffi_sync_http_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_delete) + `pactffi_sync_http_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_delete) """ lib.pactffi_sync_http_delete(interaction) @@ -3517,7 +3517,7 @@ def sync_http_get_request(interaction: SynchronousHttp) -> HttpRequest: Get the request of a `SynchronousHttp` interaction. [Rust - `pactffi_sync_http_get_request`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request) + `pactffi_sync_http_get_request`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_request) # Safety @@ -3537,7 +3537,7 @@ def sync_http_get_request_contents(interaction: SynchronousHttp) -> str | None: Get the request contents of a `SynchronousHttp` interaction in string form. [Rust - `pactffi_sync_http_get_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request_contents) + `pactffi_sync_http_get_request_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_request_contents) Note that this function will return `None` if either the body is missing or is `null`. @@ -3557,7 +3557,7 @@ def sync_http_set_request_contents( Sets the request contents of the interaction. [Rust - `pactffi_sync_http_set_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_set_request_contents) + `pactffi_sync_http_set_request_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_set_request_contents) - `interaction` - the interaction to set the request contents for - `contents` - pointer to contents to copy from. Must be a valid @@ -3585,7 +3585,7 @@ def sync_http_get_request_contents_length(interaction: SynchronousHttp) -> int: Get the length of the request contents of a `SynchronousHttp` interaction. [Rust - `pactffi_sync_http_get_request_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request_contents_length) + `pactffi_sync_http_get_request_contents_length`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_request_contents_length) This function will return 0 if the body is missing. """ @@ -3597,7 +3597,7 @@ def sync_http_get_request_contents_bin(interaction: SynchronousHttp) -> bytes | Get the request contents of a `SynchronousHttp` interaction as bytes. [Rust - `pactffi_sync_http_get_request_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request_contents_bin) + `pactffi_sync_http_get_request_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_request_contents_bin) Note that this function will return `None` if either the body is missing or is `null`. @@ -3621,7 +3621,7 @@ def sync_http_set_request_contents_bin( Sets the request contents of the interaction as an array of bytes. [Rust - `pactffi_sync_http_set_request_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_set_request_contents_bin) + `pactffi_sync_http_set_request_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_set_request_contents_bin) - `interaction` - the interaction to set the request contents for - `contents` - pointer to contents to copy from @@ -3648,7 +3648,7 @@ def sync_http_get_response(interaction: SynchronousHttp) -> HttpResponse: Get the response of a `SynchronousHttp` interaction. [Rust - `pactffi_sync_http_get_response`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response) + `pactffi_sync_http_get_response`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_response) # Safety @@ -3668,7 +3668,7 @@ def sync_http_get_response_contents(interaction: SynchronousHttp) -> str | None: Get the response contents of a `SynchronousHttp` interaction in string form. [Rust - `pactffi_sync_http_get_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response_contents) + `pactffi_sync_http_get_response_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_response_contents) Note that this function will return `None` if either the body is missing or is `null`. @@ -3688,7 +3688,7 @@ def sync_http_set_response_contents( Sets the response contents of the interaction. [Rust - `pactffi_sync_http_set_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_set_response_contents) + `pactffi_sync_http_set_response_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_set_response_contents) - `interaction` - the interaction to set the response contents for - `contents` - pointer to contents to copy from. Must be a valid @@ -3716,7 +3716,7 @@ def sync_http_get_response_contents_length(interaction: SynchronousHttp) -> int: Get the length of the response contents of a `SynchronousHttp` interaction. [Rust - `pactffi_sync_http_get_response_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response_contents_length) + `pactffi_sync_http_get_response_contents_length`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_response_contents_length) This function will return 0 if the body is missing. """ @@ -3728,7 +3728,7 @@ def sync_http_get_response_contents_bin(interaction: SynchronousHttp) -> bytes | Get the response contents of a `SynchronousHttp` interaction as bytes. [Rust - `pactffi_sync_http_get_response_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response_contents_bin) + `pactffi_sync_http_get_response_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_response_contents_bin) Note that this function will return `None` if either the body is missing or is `null`. @@ -3752,7 +3752,7 @@ def sync_http_set_response_contents_bin( Sets the response contents of the `SynchronousHttp` interaction as bytes. [Rust - `pactffi_sync_http_set_response_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_set_response_contents_bin) + `pactffi_sync_http_set_response_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_set_response_contents_bin) - `interaction` - the interaction to set the response contents for - `contents` - pointer to contents to copy from @@ -3779,7 +3779,7 @@ def sync_http_get_description(interaction: SynchronousHttp) -> str: Get a copy of the description. [Rust - `pactffi_sync_http_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_description) + `pactffi_sync_http_get_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_description) Raises: RuntimeError: @@ -3797,7 +3797,7 @@ def sync_http_set_description(interaction: SynchronousHttp, description: str) -> Write the `description` field on the `SynchronousHttp`. [Rust - `pactffi_sync_http_set_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_set_description) + `pactffi_sync_http_set_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_set_description) # Safety @@ -3822,7 +3822,7 @@ def sync_http_get_provider_state( Get a copy of the provider state at the given index from this interaction. [Rust - `pactffi_sync_http_get_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_provider_state) + `pactffi_sync_http_get_provider_state`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_provider_state) # Safety @@ -3848,7 +3848,7 @@ def sync_http_get_provider_state_iter( Get an iterator over provider states. [Rust - `pactffi_sync_http_get_provider_state_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_provider_state_iter) + `pactffi_sync_http_get_provider_state_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_provider_state_iter) # Safety @@ -3877,7 +3877,7 @@ def pact_interaction_as_synchronous_http( longer required. [Rust - `pactffi_pact_interaction_as_synchronous_http`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_as_synchronous_http) + `pactffi_pact_interaction_as_synchronous_http`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_as_synchronous_http) # Safety This function is safe as long as the interaction pointer is a valid pointer. @@ -3899,7 +3899,7 @@ def pact_interaction_as_asynchronous_message( no longer required. [Rust - `pactffi_pact_interaction_as_asynchronous_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_as_asynchronous_message) + `pactffi_pact_interaction_as_asynchronous_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_as_asynchronous_message) Note that if the interaction is a V3 `Message`, it will be converted to a V4 `AsynchronousMessage` before being returned. @@ -3924,7 +3924,7 @@ def pact_interaction_as_synchronous_message( no longer required. [Rust - `pactffi_pact_interaction_as_synchronous_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_as_synchronous_message) + `pactffi_pact_interaction_as_synchronous_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_as_synchronous_message) # Safety This function is safe as long as the interaction pointer is a valid pointer. @@ -3939,7 +3939,7 @@ def pact_async_message_iter_next(iter: PactAsyncMessageIterator) -> Asynchronous Get the next asynchronous message from the iterator. [Rust - `pactffi_pact_async_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_async_message_iter_next) + `pactffi_pact_async_message_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_async_message_iter_next) Raises: StopIteration: @@ -3956,7 +3956,7 @@ def pact_async_message_iter_delete(iter: PactAsyncMessageIterator) -> None: Free the iterator when you're done using it. [Rust - `pactffi_pact_async_message_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_async_message_iter_delete) + `pactffi_pact_async_message_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_async_message_iter_delete) """ lib.pactffi_pact_async_message_iter_delete(iter._ptr) @@ -3966,7 +3966,7 @@ def pact_sync_message_iter_next(iter: PactSyncMessageIterator) -> SynchronousMes Get the next synchronous request/response message from the V4 pact. [Rust - `pactffi_pact_sync_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_sync_message_iter_next) + `pactffi_pact_sync_message_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_message_iter_next) Raises: StopIteration: @@ -3983,7 +3983,7 @@ def pact_sync_message_iter_delete(iter: PactSyncMessageIterator) -> None: Free the iterator when you're done using it. [Rust - `pactffi_pact_sync_message_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_sync_message_iter_delete) + `pactffi_pact_sync_message_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_message_iter_delete) """ lib.pactffi_pact_sync_message_iter_delete(iter._ptr) @@ -3993,7 +3993,7 @@ def pact_sync_http_iter_next(iter: PactSyncHttpIterator) -> SynchronousHttp: Get the next synchronous HTTP request/response interaction from the V4 pact. [Rust - `pactffi_pact_sync_http_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_sync_http_iter_next) + `pactffi_pact_sync_http_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_http_iter_next) Raises: StopIteration: @@ -4010,7 +4010,7 @@ def pact_sync_http_iter_delete(iter: PactSyncHttpIterator) -> None: Free the iterator when you're done using it. [Rust - `pactffi_pact_sync_http_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_sync_http_iter_delete) + `pactffi_pact_sync_http_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_http_iter_delete) """ lib.pactffi_pact_sync_http_iter_delete(iter._ptr) @@ -4020,7 +4020,7 @@ def pact_interaction_iter_next(iter: PactInteractionIterator) -> PactInteraction Get the next interaction from the pact. [Rust - `pactffi_pact_interaction_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_iter_next) + `pactffi_pact_interaction_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_iter_next) Raises: StopIteration: @@ -4038,7 +4038,7 @@ def pact_interaction_iter_delete(iter: PactInteractionIterator) -> None: Free the iterator when you're done using it. [Rust - `pactffi_pact_interaction_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_iter_delete) + `pactffi_pact_interaction_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_iter_delete) """ lib.pactffi_pact_interaction_iter_delete(iter._ptr) @@ -4048,7 +4048,7 @@ def matching_rule_to_json(rule: MatchingRule) -> str: Get the JSON form of the matching rule. [Rust - `pactffi_matching_rule_to_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_to_json) + `pactffi_matching_rule_to_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_to_json) The returned string must be deleted with `pactffi_string_delete`. @@ -4065,7 +4065,7 @@ def matching_rules_iter_delete(iter: MatchingRuleCategoryIterator) -> None: Free the iterator when you're done using it. [Rust - `pactffi_matching_rules_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rules_iter_delete) + `pactffi_matching_rules_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rules_iter_delete) """ lib.pactffi_matching_rules_iter_delete(iter._ptr) @@ -4077,7 +4077,7 @@ def matching_rules_iter_next( Get the next path and matching rule out of the iterator, if possible. [Rust - `pactffi_matching_rules_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rules_iter_next) + `pactffi_matching_rules_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rules_iter_next) The returned pointer must be deleted with `pactffi_matching_rules_iter_pair_delete`. @@ -4099,7 +4099,7 @@ def matching_rules_iter_pair_delete(pair: MatchingRuleKeyValuePair) -> None: Free a pair of key and value returned from `message_metadata_iter_next`. [Rust - `pactffi_matching_rules_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rules_iter_pair_delete) + `pactffi_matching_rules_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rules_iter_pair_delete) """ lib.pactffi_matching_rules_iter_pair_delete(pair._ptr) @@ -4109,7 +4109,7 @@ def provider_state_iter_next(iter: ProviderStateIterator) -> ProviderState: Get the next value from the iterator. [Rust - `pactffi_provider_state_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_iter_next) + `pactffi_provider_state_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_iter_next) # Safety @@ -4130,7 +4130,7 @@ def provider_state_iter_delete(iter: ProviderStateIterator) -> None: Delete the iterator. [Rust - `pactffi_provider_state_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_iter_delete) + `pactffi_provider_state_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_iter_delete) """ lib.pactffi_provider_state_iter_delete(iter._ptr) @@ -4140,7 +4140,7 @@ def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadata Get the next key and value out of the iterator, if possible. [Rust - `pactffi_message_metadata_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_metadata_iter_next) + `pactffi_message_metadata_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_metadata_iter_next) The returned pointer must be deleted with `pactffi_message_metadata_pair_delete`. @@ -4166,7 +4166,7 @@ def message_metadata_iter_delete(iter: MessageMetadataIterator) -> None: Free the metadata iterator when you're done using it. [Rust - `pactffi_message_metadata_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_metadata_iter_delete) + `pactffi_message_metadata_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_metadata_iter_delete) """ lib.pactffi_message_metadata_iter_delete(iter._ptr) @@ -4176,7 +4176,7 @@ def message_metadata_pair_delete(pair: MessageMetadataPair) -> None: Free a pair of key and value returned from `message_metadata_iter_next`. [Rust - `pactffi_message_metadata_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_metadata_pair_delete) + `pactffi_message_metadata_pair_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_metadata_pair_delete) """ lib.pactffi_message_metadata_pair_delete(pair._ptr) @@ -4186,7 +4186,7 @@ def provider_get_name(provider: Provider) -> str: Get a copy of this provider's name. [Rust - `pactffi_provider_get_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_get_name) + `pactffi_provider_get_name`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_get_name) The copy must be deleted with `pactffi_string_delete`. @@ -4232,7 +4232,7 @@ def pact_get_provider(pact: Pact) -> Provider: `pactffi_pact_provider_delete` when no longer required. [Rust - `pactffi_pact_get_provider`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_get_provider) + `pactffi_pact_get_provider`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_get_provider) # Errors @@ -4247,7 +4247,7 @@ def pact_provider_delete(provider: Provider) -> None: Frees the memory used by the Pact provider. [Rust - `pactffi_pact_provider_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_provider_delete) + `pactffi_pact_provider_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_provider_delete) """ raise NotImplementedError @@ -4257,7 +4257,7 @@ def provider_state_get_name(provider_state: ProviderState) -> str | None: Get the name of the provider state as a string. [Rust - `pactffi_provider_state_get_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_get_name) + `pactffi_provider_state_get_name`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_get_name) Raises: RuntimeError: @@ -4277,7 +4277,7 @@ def provider_state_get_param_iter( Get an iterator over the params of a provider state. [Rust - `pactffi_provider_state_get_param_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_get_param_iter) + `pactffi_provider_state_get_param_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_get_param_iter) # Safety @@ -4305,7 +4305,7 @@ def provider_state_param_iter_next( Get the next key and value out of the iterator, if possible. [Rust - `pactffi_provider_state_param_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_param_iter_next) + `pactffi_provider_state_param_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_param_iter_next) # Safety @@ -4326,7 +4326,7 @@ def provider_state_delete(provider_state: ProviderState) -> None: Free the provider state when you're done using it. [Rust - `pactffi_provider_state_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_delete) + `pactffi_provider_state_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_delete) """ raise NotImplementedError @@ -4336,7 +4336,7 @@ def provider_state_param_iter_delete(iter: ProviderStateParamIterator) -> None: Free the provider state param iterator when you're done using it. [Rust - `pactffi_provider_state_param_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_param_iter_delete) + `pactffi_provider_state_param_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_param_iter_delete) """ lib.pactffi_provider_state_param_iter_delete(iter._ptr) @@ -4346,7 +4346,7 @@ def provider_state_param_pair_delete(pair: ProviderStateParamPair) -> None: Free a pair of key and value. [Rust - `pactffi_provider_state_param_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_param_pair_delete) + `pactffi_provider_state_param_pair_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_param_pair_delete) """ lib.pactffi_provider_state_param_pair_delete(pair._ptr) @@ -4356,7 +4356,7 @@ def sync_message_new() -> SynchronousMessage: Get a mutable pointer to a newly-created default message on the heap. [Rust - `pactffi_sync_message_new`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_new) + `pactffi_sync_message_new`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_new) # Safety @@ -4374,7 +4374,7 @@ def sync_message_delete(message: SynchronousMessage) -> None: Destroy the `Message` being pointed to. [Rust - `pactffi_sync_message_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_delete) + `pactffi_sync_message_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_delete) """ lib.pactffi_sync_message_delete(message._ptr) @@ -4384,7 +4384,7 @@ def sync_message_get_request_contents_str(message: SynchronousMessage) -> str: Get the request contents of a `SynchronousMessage` in string form. [Rust - `pactffi_sync_message_get_request_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_request_contents_str) + `pactffi_sync_message_get_request_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_request_contents_str) # Safety @@ -4411,7 +4411,7 @@ def sync_message_set_request_contents_str( Sets the request contents of the message. [Rust - `pactffi_sync_message_set_request_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_set_request_contents_str) + `pactffi_sync_message_set_request_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_set_request_contents_str) - `message` - the message to set the request contents for - `contents` - pointer to contents to copy from. Must be a valid @@ -4439,7 +4439,7 @@ def sync_message_get_request_contents_length(message: SynchronousMessage) -> int Get the length of the request contents of a `SynchronousMessage`. [Rust - `pactffi_sync_message_get_request_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_request_contents_length) + `pactffi_sync_message_get_request_contents_length`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_request_contents_length) # Safety @@ -4458,7 +4458,7 @@ def sync_message_get_request_contents_bin(message: SynchronousMessage) -> bytes: Get the request contents of a `SynchronousMessage` as a bytes. [Rust - `pactffi_sync_message_get_request_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_request_contents_bin) + `pactffi_sync_message_get_request_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_request_contents_bin) # Safety @@ -4485,7 +4485,7 @@ def sync_message_set_request_contents_bin( Sets the request contents of the message as an array of bytes. [Rust - `pactffi_sync_message_set_request_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_set_request_contents_bin) + `pactffi_sync_message_set_request_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_set_request_contents_bin) * `message` - the message to set the request contents for * `contents` - pointer to contents to copy from @@ -4512,7 +4512,7 @@ def sync_message_get_request_contents(message: SynchronousMessage) -> MessageCon Get the request contents of an `SynchronousMessage` as a `MessageContents`. [Rust - `pactffi_sync_message_get_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_request_contents) + `pactffi_sync_message_get_request_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_request_contents) # Safety @@ -4539,7 +4539,7 @@ def sync_message_generate_request_contents( contents as would be received by the consumer. [Rust - `pactffi_sync_message_generate_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_request_contents) + `pactffi_sync_message_generate_request_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_generate_request_contents) Raises: RuntimeError: @@ -4557,7 +4557,7 @@ def sync_message_get_number_responses(message: SynchronousMessage) -> int: Get the number of response messages in the `SynchronousMessage`. [Rust - `pactffi_sync_message_get_number_responses`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_number_responses) + `pactffi_sync_message_get_number_responses`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_number_responses) If the message is null, this function will return 0. """ @@ -4572,7 +4572,7 @@ def sync_message_get_response_contents_str( Get the response contents of a `SynchronousMessage` in string form. [Rust - `pactffi_sync_message_get_response_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_response_contents_str) + `pactffi_sync_message_get_response_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_response_contents_str) # Safety @@ -4605,7 +4605,7 @@ def sync_message_set_response_contents_str( with default values. [Rust - `pactffi_sync_message_set_response_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_set_response_contents_str) + `pactffi_sync_message_set_response_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_set_response_contents_str) * `message` - the message to set the response contents for * `index` - index of the response to set. 0 is the first response. @@ -4637,7 +4637,7 @@ def sync_message_get_response_contents_length( Get the length of the response contents of a `SynchronousMessage`. [Rust - `pactffi_sync_message_get_response_contents_length`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_response_contents_length) + `pactffi_sync_message_get_response_contents_length`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_response_contents_length) # Safety @@ -4659,7 +4659,7 @@ def sync_message_get_response_contents_bin( Get the response contents of a `SynchronousMessage` as bytes. [Rust - `pactffi_sync_message_get_response_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_response_contents_bin) + `pactffi_sync_message_get_response_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_response_contents_bin) # Safety @@ -4690,7 +4690,7 @@ def sync_message_set_response_contents_bin( responses will be padded with default values. [Rust - `pactffi_sync_message_set_response_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_set_response_contents_bin) + `pactffi_sync_message_set_response_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_set_response_contents_bin) * `message` - the message to set the response contents for * `index` - index of the response to set. 0 is the first response @@ -4721,7 +4721,7 @@ def sync_message_get_response_contents( Get the response contents of an `SynchronousMessage` as a `MessageContents`. [Rust - `pactffi_sync_message_get_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_response_contents) + `pactffi_sync_message_get_response_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_response_contents) # Safety @@ -4750,7 +4750,7 @@ def sync_message_generate_response_contents( received by the consumer. [Rust - `pactffi_sync_message_generate_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_generate_response_contents) + `pactffi_sync_message_generate_response_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_generate_response_contents) Raises: RuntimeError: @@ -4768,7 +4768,7 @@ def sync_message_get_description(message: SynchronousMessage) -> str: Get a copy of the description. [Rust - `pactffi_sync_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_description) + `pactffi_sync_message_get_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_description) Raises: RuntimeError: @@ -4786,7 +4786,7 @@ def sync_message_set_description(message: SynchronousMessage, description: str) Write the `description` field on the `SynchronousMessage`. [Rust - `pactffi_sync_message_set_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_set_description) + `pactffi_sync_message_set_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_set_description) # Safety @@ -4811,7 +4811,7 @@ def sync_message_get_provider_state( Get a copy of the provider state at the given index from this message. [Rust - `pactffi_sync_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_provider_state) + `pactffi_sync_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_provider_state) # Safety @@ -4837,7 +4837,7 @@ def sync_message_get_provider_state_iter( Get an iterator over provider states. [Rust - `pactffi_sync_message_get_provider_state_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_provider_state_iter) + `pactffi_sync_message_get_provider_state_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_provider_state_iter) # Safety @@ -4859,7 +4859,7 @@ def string_delete(string: OwnedString) -> None: Delete a string previously returned by this FFI. [Rust - `pactffi_string_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_string_delete) + `pactffi_string_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_string_delete) """ lib.pactffi_string_delete(string._ptr) @@ -4874,7 +4874,7 @@ def create_mock_server(pact_str: str, addr_str: str, *, tls: bool) -> int: the mock server is returned. [Rust - `pactffi_create_mock_server`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_create_mock_server) + `pactffi_create_mock_server`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_create_mock_server) * `pact_str` - Pact JSON * `addr_str` - Address to bind to in the form name:port (i.e. 127.0.0.1:80) @@ -4910,7 +4910,7 @@ def get_tls_ca_certificate() -> OwnedString: Fetch the CA Certificate used to generate the self-signed certificate. [Rust - `pactffi_get_tls_ca_certificate`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_get_tls_ca_certificate) + `pactffi_get_tls_ca_certificate`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_get_tls_ca_certificate) **NOTE:** The string for the result is allocated on the heap, and will have to be freed by the caller using pactffi_string_delete. @@ -4931,7 +4931,7 @@ def create_mock_server_for_pact(pact: PactHandle, addr_str: str, *, tls: bool) - operating system. The port of the mock server is returned. [Rust - `pactffi_create_mock_server_for_pact`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_create_mock_server_for_pact) + `pactffi_create_mock_server_for_pact`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_create_mock_server_for_pact) * `pact` - Handle to a Pact model created with created with `pactffi_new_pact`. @@ -4974,7 +4974,7 @@ def create_mock_server_for_transport( Create a mock server for the provided Pact handle and transport. [Rust - `pactffi_create_mock_server_for_transport`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_create_mock_server_for_transport) + `pactffi_create_mock_server_for_transport`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_create_mock_server_for_transport) Args: pact: @@ -5036,7 +5036,7 @@ def mock_server_matched(mock_server_handle: PactServerHandle) -> bool: if any request has not been successfully matched, or the method panics. [Rust - `pactffi_mock_server_matched`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mock_server_matched) + `pactffi_mock_server_matched`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mock_server_matched) """ return lib.pactffi_mock_server_matched(mock_server_handle._ref) @@ -5048,7 +5048,7 @@ def mock_server_mismatches( External interface to get all the mismatches from a mock server. [Rust - `pactffi_mock_server_mismatches`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mock_server_mismatches) + `pactffi_mock_server_mismatches`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mock_server_mismatches) # Errors @@ -5075,7 +5075,7 @@ def cleanup_mock_server(mock_server_handle: PactServerHandle) -> None: and cleanup any memory allocated for it. [Rust - `pactffi_cleanup_mock_server`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_cleanup_mock_server) + `pactffi_cleanup_mock_server`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_cleanup_mock_server) Args: mock_server_handle: @@ -5104,7 +5104,7 @@ def write_pact_file( directory to write the file to is passed as the second parameter. [Rust - `pactffi_write_pact_file`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_write_pact_file) + `pactffi_write_pact_file`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_write_pact_file) Args: mock_server_handle: @@ -5156,7 +5156,7 @@ def mock_server_logs(mock_server_handle: PactServerHandle) -> str: started. [Rust - `pactffi_mock_server_logs`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mock_server_logs) + `pactffi_mock_server_logs`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mock_server_logs) Raises: RuntimeError: @@ -5180,7 +5180,7 @@ def generate_datetime_string(format: str) -> StringResult: string needs to be freed with the `pactffi_string_delete` function [Rust - `pactffi_generate_datetime_string`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generate_datetime_string) + `pactffi_generate_datetime_string`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generate_datetime_string) # Safety @@ -5197,7 +5197,7 @@ def check_regex(regex: str, example: str) -> bool: Checks that the example string matches the given regex. [Rust - `pactffi_check_regex`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_check_regex) + `pactffi_check_regex`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_check_regex) # Safety @@ -5216,7 +5216,7 @@ def generate_regex_value(regex: str) -> StringResult: `pactffi_string_delete` function. [Rust - `pactffi_generate_regex_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generate_regex_value) + `pactffi_generate_regex_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generate_regex_value) # Safety @@ -5231,7 +5231,7 @@ def free_string(s: str) -> None: [DEPRECATED] Frees the memory allocated to a string by another function. [Rust - `pactffi_free_string`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_free_string) + `pactffi_free_string`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_free_string) This function is deprecated. Use `pactffi_string_delete` instead. @@ -5253,7 +5253,7 @@ def new_pact(consumer_name: str, provider_name: str) -> PactHandle: Creates a new Pact model and returns a handle to it. [Rust - `pactffi_new_pact`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_new_pact) + `pactffi_new_pact`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_new_pact) Args: consumer_name: @@ -5294,7 +5294,7 @@ def new_interaction(pact: PactHandle, description: str) -> InteractionHandle: will result in that interaction being replaced with the new one. [Rust - `pactffi_new_interaction`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_new_interaction) + `pactffi_new_interaction`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_new_interaction) Args: pact: @@ -5322,7 +5322,7 @@ def new_message_interaction(pact: PactHandle, description: str) -> InteractionHa will result in that interaction being replaced with the new one. [Rust - `pactffi_new_message_interaction`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_new_message_interaction) + `pactffi_new_message_interaction`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_new_message_interaction) Args: pact: @@ -5353,7 +5353,7 @@ def new_sync_message_interaction( will result in that interaction being replaced with the new one. [Rust - `pactffi_new_sync_message_interaction`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_new_sync_message_interaction) + `pactffi_new_sync_message_interaction`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_new_sync_message_interaction) Args: pact: @@ -5378,7 +5378,7 @@ def upon_receiving(interaction: InteractionHandle, description: str) -> None: Sets the description for the Interaction. [Rust - `pactffi_upon_receiving`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_upon_receiving) + `pactffi_upon_receiving`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_upon_receiving) This function @@ -5419,7 +5419,7 @@ def given(interaction: InteractionHandle, description: str) -> None: Adds a provider state to the Interaction. [Rust - `pactffi_given`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_given) + `pactffi_given`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_given) Args: interaction: @@ -5446,7 +5446,7 @@ def interaction_test_name(interaction: InteractionHandle, test_name: str) -> Non used with V4 interactions. [Rust - `pactffi_interaction_test_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_interaction_test_name) + `pactffi_interaction_test_name`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_interaction_test_name) Args: interaction: @@ -5493,7 +5493,7 @@ def given_with_param( be parsed as JSON. [Rust - `pactffi_given_with_param`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_given_with_param) + `pactffi_given_with_param`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_given_with_param) Args: interaction: @@ -5535,7 +5535,7 @@ def given_with_params( with a `value` key. [Rust - `pactffi_given_with_params`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_given_with_params) + `pactffi_given_with_params`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_given_with_params) Args: interaction: @@ -5574,7 +5574,7 @@ def with_request(interaction: InteractionHandle, method: str, path: str) -> None Configures the request for the Interaction. [Rust - `pactffi_with_request`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_request) + `pactffi_with_request`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_request) Args: interaction: @@ -5588,7 +5588,7 @@ def with_request(interaction: InteractionHandle, method: str, path: str) -> None This may be a simple string in which case it will be used as-is, or it may be a [JSON matching - rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md) + rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md) which allows regex patterns. For examples: ```json @@ -5623,7 +5623,7 @@ def with_query_parameter_v2( Configures a query parameter for the Interaction. [Rust - `pactffi_with_query_parameter_v2`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_query_parameter_v2) + `pactffi_with_query_parameter_v2`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_query_parameter_v2) To setup a query parameter with multiple values, you can either call this function multiple times with a different index value: @@ -5660,7 +5660,7 @@ def with_query_parameter_v2( ) ``` - See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md) + See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md) If you want the matching rules to apply to all values (and not just the one with the given index), make sure to set the value to be an array. @@ -5712,7 +5712,7 @@ def with_query_parameter_v2( This may be a simple string in which case it will be used as-is, or it may be a [JSON matching - rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). + rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md). Raises: RuntimeError: @@ -5734,7 +5734,7 @@ def with_specification(pact: PactHandle, version: PactSpecification) -> None: Sets the specification version for a given Pact model. [Rust - `pactffi_with_specification`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_specification) + `pactffi_with_specification`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_specification) Args: pact: @@ -5758,7 +5758,7 @@ def handle_get_pact_spec_version(handle: PactHandle) -> PactSpecification: Fetches the Pact specification version for the given Pact model. [Rust - `pactffi_handle_get_pact_spec_version`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_handle_get_pact_spec_version) + `pactffi_handle_get_pact_spec_version`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_handle_get_pact_spec_version) Args: handle: @@ -5784,7 +5784,7 @@ def with_pact_metadata( mock server for it has already started) [Rust - `pactffi_with_pact_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_pact_metadata) + `pactffi_with_pact_metadata`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_pact_metadata) Args: pact: @@ -5847,7 +5847,7 @@ def with_metadata( ``` See - [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md) + [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md) # Note @@ -5896,7 +5896,7 @@ def with_header_v2( r""" Configures a header for the Interaction. - [Rust `pactffi_with_header_v2`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_header_v2) + [Rust `pactffi_with_header_v2`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_header_v2) To setup a header with multiple values, you can either call this function multiple times with a different index value: @@ -5934,7 +5934,7 @@ def with_header_v2( ) ``` - See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md) + See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md) Args: interaction: @@ -5956,7 +5956,7 @@ def with_header_v2( This may be a simple string in which case it will be used as-is, or it may be a [JSON matching - rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). + rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md). Raises: RuntimeError: @@ -5988,7 +5988,7 @@ def set_header( and generators can not be configured with it. [Rust - `pactffi_set_header`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_set_header) + `pactffi_set_header`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_set_header) If matching rules are required to be set, use `pactffi_with_header_v2`. @@ -6026,7 +6026,7 @@ def response_status(interaction: InteractionHandle, status: int) -> None: Configures the response for the Interaction. [Rust - `pactffi_response_status`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_response_status) + `pactffi_response_status`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_response_status) Args: interaction: @@ -6050,7 +6050,7 @@ def response_status_v2(interaction: InteractionHandle, status: str) -> None: Configures the response for the Interaction. [Rust - `pactffi_response_status_v2`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_response_status_v2) + `pactffi_response_status_v2`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_response_status_v2) To include matching rules for the status (only statusCode or integer really makes sense to use), include the matching rule JSON format with the value as @@ -6069,7 +6069,7 @@ def response_status_v2(interaction: InteractionHandle, status: str) -> None: ) ``` - See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md) + See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md) Args: interaction: @@ -6080,7 +6080,7 @@ def response_status_v2(interaction: InteractionHandle, status: str) -> None: This may be a simple string in which case it will be used as-is, or it may be a [JSON matching - rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). + rule](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md). Raises: RuntimeError: @@ -6104,7 +6104,7 @@ def with_body( Adds the body for the interaction. [Rust - `pactffi_with_body`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_body) + `pactffi_with_body`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_body) For HTTP and async message interactions, this will overwrite the body. With asynchronous messages, the part parameter will be ignored. With synchronous @@ -6125,7 +6125,7 @@ def with_body( body: The body contents. For JSON payloads, matching rules can be embedded - in the body. See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). + in the body. See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md). Raises: RuntimeError: @@ -6152,7 +6152,7 @@ def with_binary_body( Adds the body for the interaction. [Rust - `pactffi_with_binary_body`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_binary_body) + `pactffi_with_binary_body`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_binary_body) For HTTP and async message interactions, this will overwrite the body. With asynchronous messages, the part parameter will be ignored. With synchronous @@ -6196,7 +6196,7 @@ def with_binary_file( already started) [Rust - `pactffi_with_binary_file`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_binary_file) + `pactffi_with_binary_file`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_binary_file) For HTTP and async message interactions, this will overwrite the body. With asynchronous messages, the part parameter will be ignored. With synchronous @@ -6250,7 +6250,7 @@ def with_matching_rules( Add matching rules to the interaction. [Rust - `pactffi_with_matching_rules`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_matching_rules) + `pactffi_with_matching_rules`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_matching_rules) This function can be called multiple times, in which case the matching rules will be merged. @@ -6288,7 +6288,7 @@ def with_generators( Add generators to the interaction. [Rust - `pactffi_with_generators`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_generators) + `pactffi_with_generators`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_generators) This function can be called multiple times, in which case the generators will be combined (provide they don't clash). @@ -6336,7 +6336,7 @@ def with_multipart_file_v2( # noqa: PLR0913 already started) or an error occurs. [Rust - `pactffi_with_multipart_file_v2`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_multipart_file_v2) + `pactffi_with_multipart_file_v2`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_multipart_file_v2) This function can be called multiple times. In that case, each subsequent call will be appended to the existing multipart body as a new part. @@ -6390,7 +6390,7 @@ def with_multipart_file( already started) or an error occurs. [Rust - `pactffi_with_multipart_file`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_multipart_file) + `pactffi_with_multipart_file`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_multipart_file) * `interaction` - Interaction handle to set the body for. * `part` - Request or response part. @@ -6427,7 +6427,7 @@ def set_key(interaction: InteractionHandle, key: str | None) -> None: Sets the key attribute for the interaction. [Rust - `pactffi_set_key`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_set_key) + `pactffi_set_key`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_set_key) Args: interaction: @@ -6455,7 +6455,7 @@ def set_pending(interaction: InteractionHandle, *, pending: bool) -> None: Mark the interaction as pending. [Rust - `pactffi_set_pending`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_set_pending) + `pactffi_set_pending`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_set_pending) Args: interaction: @@ -6479,7 +6479,7 @@ def set_comment(interaction: InteractionHandle, key: str, value: str | None) -> Add a comment to the interaction. [Rust - `pactffi_set_comment`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_set_comment) + `pactffi_set_comment`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_set_comment) Args: interaction: @@ -6513,7 +6513,7 @@ def add_text_comment(interaction: InteractionHandle, comment: str) -> None: Add a text comment to the interaction. [Rust - `pactffi_add_text_comment`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_add_text_comment) + `pactffi_add_text_comment`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_add_text_comment) Args: interaction: @@ -6543,7 +6543,7 @@ def pact_handle_get_async_message_iter(pact: PactHandle) -> PactAsyncMessageIter `pactffi_pact_sync_message_iter_delete`. [Rust - `pactffi_pact_handle_get_sync_message_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_sync_message_iter) + `pactffi_pact_handle_get_sync_message_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_handle_get_sync_message_iter) # Safety @@ -6569,7 +6569,7 @@ def pact_handle_get_sync_message_iter(pact: PactHandle) -> PactSyncMessageIterat `pactffi_pact_sync_message_iter_delete`. [Rust - `pactffi_pact_handle_get_sync_message_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_sync_message_iter) + `pactffi_pact_handle_get_sync_message_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_handle_get_sync_message_iter) # Safety @@ -6595,7 +6595,7 @@ def pact_handle_get_sync_http_iter(pact: PactHandle) -> PactSyncHttpIterator: `pactffi_pact_sync_http_iter_delete`. [Rust - `pactffi_pact_handle_get_sync_http_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_sync_http_iter) + `pactffi_pact_handle_get_sync_http_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_handle_get_sync_http_iter) # Safety @@ -6621,7 +6621,7 @@ def pact_handle_write_file( External interface to write out the pact file. [Rust - `pactffi_pact_handle_write_file`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_write_file) + `pactffi_pact_handle_write_file`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_handle_write_file) This function should be called if all the consumer tests have passed. @@ -6665,7 +6665,7 @@ def free_pact_handle(pact: PactHandle) -> None: Delete a Pact handle and free the resources used by it. [Rust - `pactffi_free_pact_handle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_free_pact_handle) + `pactffi_free_pact_handle`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_free_pact_handle) Raises: RuntimeError: @@ -6685,7 +6685,7 @@ def verify(args: str) -> int: """ External interface to verifier a provider. - [Rust `pactffi_verify`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verify) + [Rust `pactffi_verify`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verify) * `args` - the same as the CLI interface, except newline delimited @@ -6712,7 +6712,7 @@ def verifier_new_for_application() -> VerifierHandle: Get a Handle to a newly created verifier. [Rust - `pactffi_verifier_new_for_application`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_new_for_application) + `pactffi_verifier_new_for_application`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_new_for_application) """ from pact import __version__ @@ -6727,7 +6727,7 @@ def verifier_shutdown(handle: VerifierHandle) -> None: """ Shutdown the verifier and release all resources. - [Rust `pactffi_verifier_shutdown`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_shutdown) + [Rust `pactffi_verifier_shutdown`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_shutdown) """ lib.pactffi_verifier_shutdown(handle._ref) @@ -6744,7 +6744,7 @@ def verifier_set_provider_info( # noqa: PLR0913 Set the provider details for the Pact verifier. [Rust - `pactffi_verifier_set_provider_info`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_provider_info) + `pactffi_verifier_set_provider_info`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_provider_info) Args: handle: @@ -6790,7 +6790,7 @@ def verifier_add_provider_transport( Adds a new transport for the given provider. [Rust - `pactffi_verifier_add_provider_transport`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_add_provider_transport) + `pactffi_verifier_add_provider_transport`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_add_provider_transport) Args: handle: @@ -6831,7 +6831,7 @@ def verifier_set_filter_info( Set the filters for the Pact verifier. [Rust - `pactffi_verifier_set_filter_info`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_filter_info) + `pactffi_verifier_set_filter_info`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_filter_info) Set filters to narrow down the interactions to verify. @@ -6867,7 +6867,7 @@ def verifier_set_provider_state( Set the provider state URL for the Pact verifier. [Rust - `pactffi_verifier_set_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_provider_state) + `pactffi_verifier_set_provider_state`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_provider_state) Args: handle: @@ -6902,7 +6902,7 @@ def verifier_set_verification_options( Set the options used by the verifier when calling the provider. [Rust - `pactffi_verifier_set_verification_options`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_verification_options) + `pactffi_verifier_set_verification_options`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_verification_options) Args: handle: @@ -6937,7 +6937,7 @@ def verifier_set_coloured_output( Enables or disables coloured output using ANSI escape codes. [Rust - `pactffi_verifier_set_coloured_output`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_coloured_output) + `pactffi_verifier_set_coloured_output`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_coloured_output) By default, coloured output is enabled. @@ -6966,7 +6966,7 @@ def verifier_set_no_pacts_is_error(handle: VerifierHandle, *, enabled: bool) -> Enables or disables if no pacts are found to verify results in an error. [Rust - `pactffi_verifier_set_no_pacts_is_error`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_no_pacts_is_error) + `pactffi_verifier_set_no_pacts_is_error`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_no_pacts_is_error) Args: handle: @@ -6999,7 +6999,7 @@ def verifier_set_publish_options( Set the options used when publishing verification results to the Broker. [Rust - `pactffi_verifier_set_publish_options`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_publish_options) + `pactffi_verifier_set_publish_options`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_publish_options) Args: handle: @@ -7042,7 +7042,7 @@ def verifier_set_consumer_filters( Set the consumer filters for the Pact verifier. [Rust - `pactffi_verifier_set_consumer_filters`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_set_consumer_filters) + `pactffi_verifier_set_consumer_filters`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_set_consumer_filters) """ lib.pactffi_verifier_set_consumer_filters( handle._ref, @@ -7060,7 +7060,7 @@ def verifier_add_custom_header( Adds a custom header to be added to the requests made to the provider. [Rust - `pactffi_verifier_add_custom_header`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_add_custom_header) + `pactffi_verifier_add_custom_header`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_add_custom_header) """ lib.pactffi_verifier_add_custom_header( handle._ref, @@ -7074,7 +7074,7 @@ def verifier_add_file_source(handle: VerifierHandle, file: str) -> None: Adds a Pact file as a source to verify. [Rust - `pactffi_verifier_add_file_source`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_add_file_source) + `pactffi_verifier_add_file_source`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_add_file_source) """ lib.pactffi_verifier_add_file_source(handle._ref, file.encode("utf-8")) @@ -7086,7 +7086,7 @@ def verifier_add_directory_source(handle: VerifierHandle, directory: str) -> Non All pacts from the directory that match the provider name will be verified. [Rust - `pactffi_verifier_add_directory_source`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_add_directory_source) + `pactffi_verifier_add_directory_source`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_add_directory_source) # Safety @@ -7108,7 +7108,7 @@ def verifier_url_source( Adds a URL as a source to verify. [Rust - `pactffi_verifier_url_source`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_url_source) + `pactffi_verifier_url_source`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_url_source) Args: handle: @@ -7148,7 +7148,7 @@ def verifier_broker_source( Adds a Pact broker as a source to verify. [Rust - `pactffi_verifier_broker_source`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_broker_source) + `pactffi_verifier_broker_source`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_broker_source) This will fetch all the pact files from the broker that match the provider name. @@ -7196,7 +7196,7 @@ def verifier_broker_source_with_selectors( # noqa: PLR0913 Adds a Pact broker as a source to verify. [Rust - `pactffi_verifier_broker_source_with_selectors`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_broker_source_with_selectors) + `pactffi_verifier_broker_source_with_selectors`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_broker_source_with_selectors) This will fetch all the pact files from the broker that match the provider name and the consumer version selectors (See [Consumer Version @@ -7263,7 +7263,7 @@ def verifier_execute(handle: VerifierHandle) -> None: """ Runs the verification. - (https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_execute) + (https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_execute) Raises: RuntimeError: @@ -7283,7 +7283,7 @@ def verifier_cli_args() -> str: string. [Rust - `pactffi_verifier_cli_args`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_cli_args) + `pactffi_verifier_cli_args`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_cli_args) The purpose is to then be able to use in other languages which wrap the FFI library, to implement the same CLI functionality automatically without @@ -7339,7 +7339,7 @@ def verifier_logs(handle: VerifierHandle) -> OwnedString: Extracts the logs for the verification run. [Rust - `pactffi_verifier_logs`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_logs) + `pactffi_verifier_logs`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_logs) This needs the memory buffer log sink to be setup before the verification is executed. The returned string will need to be freed with the `free_string` @@ -7361,7 +7361,7 @@ def verifier_logs_for_provider(provider_name: str) -> OwnedString: Extracts the logs for the verification run for the provider name. [Rust - `pactffi_verifier_logs_for_provider`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_logs_for_provider) + `pactffi_verifier_logs_for_provider`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_logs_for_provider) This needs the memory buffer log sink to be setup before the verification is executed. The returned string will need to be freed with the `free_string` @@ -7383,7 +7383,7 @@ def verifier_output(handle: VerifierHandle, strip_ansi: int) -> OwnedString: Extracts the standard output for the verification run. [Rust - `pactffi_verifier_output`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_output) + `pactffi_verifier_output`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_output) Args: handle: @@ -7410,7 +7410,7 @@ def verifier_json(handle: VerifierHandle) -> OwnedString: Extracts the verification result as a JSON document. [Rust - `pactffi_verifier_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_verifier_json) + `pactffi_verifier_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_verifier_json) Raises: RuntimeError: @@ -7438,7 +7438,7 @@ def using_plugin( otherwise you will have plugin processes left running. [Rust - `pactffi_using_plugin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_using_plugin) + `pactffi_using_plugin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_using_plugin) Args: pact: @@ -7481,7 +7481,7 @@ def cleanup_plugins(pact: PactHandle) -> None: zero). [Rust - `pactffi_cleanup_plugins`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_cleanup_plugins) + `pactffi_cleanup_plugins`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_cleanup_plugins) """ lib.pactffi_cleanup_plugins(pact._ref) @@ -7500,7 +7500,7 @@ def interaction_contents( format of the JSON contents. [Rust - `pactffi_interaction_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_interaction_contents) + `pactffi_interaction_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_interaction_contents) Args: interaction: @@ -7560,7 +7560,7 @@ def matches_string_value( function once it is no longer required. [Rust - `pactffi_matches_string_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matches_string_value) + `pactffi_matches_string_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matches_string_value) * matching_rule - pointer to a matching rule * expected_value - value we expect to get as a NULL terminated string @@ -7591,7 +7591,7 @@ def matches_u64_value( function once it is no longer required. [Rust - `pactffi_matches_u64_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matches_u64_value) + `pactffi_matches_u64_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matches_u64_value) * matching_rule - pointer to a matching rule * expected_value - value we expect to get @@ -7621,7 +7621,7 @@ def matches_i64_value( function once it is no longer required. [Rust - `pactffi_matches_i64_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matches_i64_value) + `pactffi_matches_i64_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matches_i64_value) * matching_rule - pointer to a matching rule * expected_value - value we expect to get @@ -7651,7 +7651,7 @@ def matches_f64_value( function once it is no longer required. [Rust - `pactffi_matches_f64_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matches_f64_value) + `pactffi_matches_f64_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matches_f64_value) * matching_rule - pointer to a matching rule * expected_value - value we expect to get @@ -7681,7 +7681,7 @@ def matches_bool_value( function once it is no longer required. [Rust - `pactffi_matches_bool_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matches_bool_value) + `pactffi_matches_bool_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matches_bool_value) * matching_rule - pointer to a matching rule * expected_value - value we expect to get, 0 == false and 1 == true @@ -7713,7 +7713,7 @@ def matches_binary_value( # noqa: PLR0913 function once it is no longer required. [Rust - `pactffi_matches_binary_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matches_binary_value) + `pactffi_matches_binary_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matches_binary_value) * matching_rule - pointer to a matching rule * expected_value - value we expect to get @@ -7748,7 +7748,7 @@ def matches_json_value( function once it is no longer required. [Rust - `pactffi_matches_json_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matches_json_value) + `pactffi_matches_json_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matches_json_value) * matching_rule - pointer to a matching rule * expected_value - value we expect to get as a NULL terminated string diff --git a/src/pact/v3/interaction/_http_interaction.py b/src/pact/v3/interaction/_http_interaction.py index 335d6293c..1f00bf194 100644 --- a/src/pact/v3/interaction/_http_interaction.py +++ b/src/pact/v3/interaction/_http_interaction.py @@ -149,7 +149,7 @@ def with_header( # JSON Matching Pact's matching rules are defined in the [upstream - documentation](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md) + documentation](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md) and support a wide range of matching rules. These can be specified using a JSON object as a strong using `json.dumps(...)`. For example, the above rule whereby the `X-Foo` header has multiple values can be @@ -391,7 +391,7 @@ def with_query_parameter(self, name: str, value: str) -> Self: ``` For more information on the format of the JSON object, see the [upstream - documentation](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md). + documentation](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md). Args: name: From 5cb4a8a029a5859855accbdbcb28b951d29ab01b Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:16:06 +1000 Subject: [PATCH 16/23] docs: minor refinements A couple of minor changes to the docs clarifying the role of `Interaction Part`, especially in the context of asynchronous messages. Signed-off-by: JP-Ellis --- .../v3/interaction/_async_message_interaction.py | 12 +++++++++++- src/pact/v3/interaction/_base.py | 13 +++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/pact/v3/interaction/_async_message_interaction.py b/src/pact/v3/interaction/_async_message_interaction.py index 011853af3..15621650a 100644 --- a/src/pact/v3/interaction/_async_message_interaction.py +++ b/src/pact/v3/interaction/_async_message_interaction.py @@ -33,7 +33,7 @@ def __init__(self, pact_handle: pact.v3.ffi.PactHandle, description: str) -> Non Args: pact_handle: - Handle for the Pact. + The Pact instance this interaction belongs to. description: Description of the interaction. This must be unique within the @@ -54,4 +54,14 @@ def _handle(self) -> pact.v3.ffi.InteractionHandle: @property def _interaction_part(self) -> pact.v3.ffi.InteractionPart: + """ + Interaction part. + + Where interactions have multiple parts, this property keeps track + of which part is currently being set. + + As this is an asynchronous message interaction, this will always + return a [`REQUEST`][pact.v3.ffi.InteractionPart.REQUEST], as there the + consumer of the message does not send any responses. + """ return pact.v3.ffi.InteractionPart.REQUEST diff --git a/src/pact/v3/interaction/_base.py b/src/pact/v3/interaction/_base.py index d840c4a58..9696f37a3 100644 --- a/src/pact/v3/interaction/_base.py +++ b/src/pact/v3/interaction/_base.py @@ -37,6 +37,19 @@ class Interaction(abc.ABC): - [`HttpInteraction`][pact.v3.interaction.HttpInteraction] - [`AsyncMessageInteraction`][pact.v3.interaction.AsyncMessageInteraction] - [`SyncMessageInteraction`][pact.v3.interaction.SyncMessageInteraction] + + # Interaction Part + + For HTTP and synchronous message interactions, the interaction is split into + two parts: the request and the response. The interaction part is used to + specify which part of the interaction is being set. This is specified using + the `part` argument of various methods (which defaults to an intelligent + choice based on the order of the methods called). + + The asynchronous message interaction does not have parts, as the interaction + contains a single message from the provider (a.ka. the producer of the + message) to the consumer. An attempt to set a response part will raise an + error. """ def __init__(self, description: str) -> None: From 1f635dba864d521828e888ce1bde1388989c0f93 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:17:36 +1000 Subject: [PATCH 17/23] docs(example): clarify purpose of fs interface The previous docstrings did not clearly explain the purpose for the filesystem class. As the examples are intended to be quite pedagogical, the new docstring makes it very clear what it is meant to do/why it exists. Signed-off-by: JP-Ellis --- examples/src/message.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/examples/src/message.py b/examples/src/message.py index cab137ec0..815719903 100644 --- a/examples/src/message.py +++ b/examples/src/message.py @@ -14,7 +14,22 @@ class Filesystem: - """Filesystem interface.""" + """ + Filesystem interface. + + In practice, the handler would process messages and perform some actions on + other systems, whether that be a database, a filesystem, or some other + service. This capability would typically be offered by some library; + however, when running tests, we typically wish to avoid actually interacting + with this external service. + + In order to avoid side effects while testing, the test setup should mock out + the calls to the external service. + + This class provides a simple dummy filesystem interface (which evidently + would fail if actually used), and serves to demonstrate how to mock out + external services when testing. + """ def __init__(self) -> None: """Initialize the filesystem connection.""" From 48ee615460be3f9b9b8c2ddb97ed4a4c3ea5ec90 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:40:42 +1000 Subject: [PATCH 18/23] feat(v3): improve exception types Define a new `PactError` base class, and define new errors to encapsulate verification errors. Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/pact.py | 141 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 26 deletions(-) diff --git a/src/pact/v3/pact.py b/src/pact/v3/pact.py index 231c01dd7..58c3c95d5 100644 --- a/src/pact/v3/pact.py +++ b/src/pact/v3/pact.py @@ -64,6 +64,7 @@ import json import logging +from abc import ABC from pathlib import Path from typing import TYPE_CHECKING, Any, Literal, Set, overload @@ -87,6 +88,120 @@ logger = logging.getLogger(__name__) +class PactError(Exception, ABC): + """ + Base class for exceptions raised by the Pact module. + """ + + +class InteractionVerificationError(PactError): + """ + Exception raised due during the verification of an interaction. + + This error is raised when an error occurs during the manual verification of an + interaction. This is typically raised when the consumer fails to handle the + interaction correctly thereby generating its own exception. The cause of the + error is stored in the `error` attribute. + """ + + def __init__(self, description: str, error: Exception) -> None: + """ + Initialise a new InteractionVerificationError. + + Args: + description: + Description of the interaction that failed verification. + + error: Error that occurred during the verification of the + interaction. + """ + super().__init__(f"Error verifying interaction '{description}': {error}") + self._description = description + self._error = error + + @property + def description(self) -> str: + """ + Description of the interaction that failed verification. + """ + return self._description + + @property + def error(self) -> Exception: + """ + Error that occurred during the verification of the interaction. + """ + return self._error + + +class PactVerificationError(PactError): + """ + Exception raised due to errors in the verification of a Pact. + + This is raised when performing manual verification of the Pact through the + [`verify`][pact.v3.Pact.verify] method: + + ```python + pact = Pact("consumer", "provider") + # Define interactions... + try: + pact.verify(handler, kind="Async") + except PactVerificationError as e: + print(e.errors) + ``` + + All of the errors that occurred during the verification of all of the + interactions are stored in the `errors` attribute. + + This is different from the [`MismatchesError`][pact.v3.MismatchesError] + which is raised when there are mismatches detected by the mock server. + """ + + def __init__(self, errors: list[InteractionVerificationError]) -> None: + """ + Initialise a new PactVerificationError. + + Args: + errors: + Errors that occurred during the verification of the Pact. + """ + super().__init__(f"Error verifying Pact (count: {len(errors)})") + self._errors = errors + + @property + def errors(self) -> list[InteractionVerificationError]: + """ + Errors that occurred during the verification of the Pact. + """ + return self._errors + + +class MismatchesError(PactError): + """ + Exception raised when there are mismatches between the Pact and the server. + """ + + def __init__(self, mismatches: list[dict[str, Any]]) -> None: + """ + Initialise a new MismatchesError. + + Args: + mismatches: + Mismatches between the Pact and the server. + """ + super().__init__(f"Mismatched interaction (count: {len(mismatches)})") + self._mismatches = mismatches + + # TODO: Replace the list of dicts with a more structured object. + # https://github.com/pact-foundation/pact-python/issues/644 + @property + def mismatches(self) -> list[dict[str, Any]]: + """ + Mismatches between the Pact and the server. + """ + return self._mismatches + + class Pact: """ A Pact between a consumer and a provider. @@ -443,32 +558,6 @@ def write_file( ) -class MismatchesError(Exception): - """ - Exception raised when there are mismatches between the Pact and the server. - """ - - def __init__(self, mismatches: list[dict[str, Any]]) -> None: - """ - Initialise a new MismatchesError. - - Args: - mismatches: - Mismatches between the Pact and the server. - """ - super().__init__(f"Mismatched interaction (count: {len(mismatches)})") - self._mismatches = mismatches - - # TODO: Replace the list of dicts with a more structured object. - # https://github.com/pact-foundation/pact-python/issues/644 - @property - def mismatches(self) -> list[dict[str, Any]]: - """ - Mismatches between the Pact and the server. - """ - return self._mismatches - - class PactServer: """ Pact Server. From 6e7da418f62a8699aacdd15045b036a88ca199a7 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:43:16 +1000 Subject: [PATCH 19/23] feat(v3): remove deprecated messages iterator Signed-off-by: JP-Ellis --- src/pact/v3/pact.py | 21 --------------------- tests/v3/test_pact.py | 10 ---------- 2 files changed, 31 deletions(-) diff --git a/src/pact/v3/pact.py b/src/pact/v3/pact.py index 58c3c95d5..ba5bd423c 100644 --- a/src/pact/v3/pact.py +++ b/src/pact/v3/pact.py @@ -461,27 +461,6 @@ def serve( # noqa: PLR0913 verbose=verbose, ) - def messages(self) -> pact.v3.ffi.PactMessageIterator: - """ - Iterate over the messages in the Pact. - - This function returns an iterator over the messages in the Pact. This - is useful for validating the Pact against the provider. - - ```python - pact = Pact("consumer", "provider") - with pact.serve() as srv: - for message in pact.messages(): - # Validate the message against the provider. - ... - ``` - - Note that the Pact must be written to a file before the messages can be - iterated over. This is because the messages are not stored in memory, - but rather are streamed directly from the file. - """ - return pact.v3.ffi.pact_handle_get_message_iter(self._handle) - @overload def interactions( self, diff --git a/tests/v3/test_pact.py b/tests/v3/test_pact.py index 5f32e7d80..2264346d0 100644 --- a/tests/v3/test_pact.py +++ b/tests/v3/test_pact.py @@ -93,16 +93,6 @@ def test_interactions_iter( raise RuntimeError(msg) -def test_messages(pact: Pact) -> None: - messages = pact.messages() - assert messages is not None - for _message in messages: - # This should be an empty list and therefore the error should never be - # raised. - msg = "Should not be reached" - raise RuntimeError(msg) - - def test_write_file(pact: Pact, temp_dir: Path) -> None: pact.write_file(temp_dir) outfile = temp_dir / "consumer-provider.json" From 6d3c1dcd8cf5f591cba70136c3e9c22d40e39fa6 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:44:43 +1000 Subject: [PATCH 20/23] refactor(v3): new interaction iterators Instead of returning the iterators themselves, use `yield from` constructs. This helps for two reasons: 1. Avoids needlessly exposing additional classes which serve no other purpose than to iterate over data. 2. Manages the lifetime of the iterators more clearly, preventing possible issues due to the underlying data mutating during iteration. Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/pact.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/pact/v3/pact.py b/src/pact/v3/pact.py index ba5bd423c..f9cdbfd77 100644 --- a/src/pact/v3/pact.py +++ b/src/pact/v3/pact.py @@ -66,7 +66,7 @@ import logging from abc import ABC from pathlib import Path -from typing import TYPE_CHECKING, Any, Literal, Set, overload +from typing import TYPE_CHECKING, Any, Generator, Literal, Set, overload from yarl import URL @@ -465,27 +465,27 @@ def serve( # noqa: PLR0913 def interactions( self, kind: Literal["HTTP"], - ) -> pact.v3.ffi.PactSyncHttpIterator: ... + ) -> Generator[pact.v3.ffi.SynchronousHttp, None, None]: ... @overload def interactions( self, kind: Literal["Sync"], - ) -> pact.v3.ffi.PactSyncMessageIterator: ... + ) -> Generator[pact.v3.ffi.SynchronousMessage, None, None]: ... @overload def interactions( self, kind: Literal["Async"], - ) -> pact.v3.ffi.PactMessageIterator: ... + ) -> Generator[pact.v3.ffi.AsynchronousMessage, None, None]: ... def interactions( self, - kind: str = "HTTP", + kind: Literal["HTTP", "Sync", "Async"] = "HTTP", ) -> ( - pact.v3.ffi.PactSyncHttpIterator - | pact.v3.ffi.PactSyncMessageIterator - | pact.v3.ffi.PactMessageIterator + Generator[pact.v3.ffi.SynchronousHttp, None, None] + | Generator[pact.v3.ffi.SynchronousMessage, None, None] + | Generator[pact.v3.ffi.AsynchronousMessage, None, None] ): """ Return an iterator over the Pact's interactions. @@ -496,13 +496,15 @@ def interactions( # TODO: Add an iterator for `All` interactions. # https://github.com/pact-foundation/pact-python/issues/451 if kind == "HTTP": - return pact.v3.ffi.pact_handle_get_sync_http_iter(self._handle) - if kind == "Sync": - return pact.v3.ffi.pact_handle_get_sync_message_iter(self._handle) - if kind == "Async": - return pact.v3.ffi.pact_handle_get_message_iter(self._handle) - msg = f"Unknown interaction type: {kind}" - raise ValueError(msg) + yield from pact.v3.ffi.pact_handle_get_sync_http_iter(self._handle) + elif kind == "Sync": + yield from pact.v3.ffi.pact_handle_get_sync_message_iter(self._handle) + elif kind == "Async": + yield from pact.v3.ffi.pact_handle_get_async_message_iter(self._handle) + else: + msg = f"Unknown interaction type: {kind}" + raise ValueError(msg) + return # Ensures that the parent object outlives the generator def write_file( self, From c1d970e65fd4666008b83e3582d98ed3f9a01394 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:49:01 +1000 Subject: [PATCH 21/23] feat(v3): implement message verification As messages abstract away the transport layer, it is necessary for Python to directly handle the messages through some user-defined handler. The `verify` method implements this on the consumer side by verifying that each message can be consumed. Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- src/pact/v3/pact.py | 102 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/src/pact/v3/pact.py b/src/pact/v3/pact.py index f9cdbfd77..1c0dd2fd4 100644 --- a/src/pact/v3/pact.py +++ b/src/pact/v3/pact.py @@ -64,9 +64,20 @@ import json import logging +import warnings from abc import ABC from pathlib import Path -from typing import TYPE_CHECKING, Any, Generator, Literal, Set, overload +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Generator, + List, + Literal, + Set, + overload, +) from yarl import URL @@ -506,6 +517,95 @@ def interactions( raise ValueError(msg) return # Ensures that the parent object outlives the generator + @overload + def verify( + self, + handler: Callable[[str | bytes | None, Dict[str, str]], None], + kind: Literal["Async", "Sync"], + *, + raises: Literal[True] = True, + ) -> None: ... + @overload + def verify( + self, + handler: Callable[[str | bytes | None, Dict[str, str]], None], + kind: Literal["Async", "Sync"], + *, + raises: Literal[False], + ) -> List[InteractionVerificationError]: ... + + def verify( + self, + handler: Callable[[str | bytes | None, Dict[str, str]], None], + kind: Literal["Async", "Sync"], + *, + raises: bool = True, + ) -> List[InteractionVerificationError] | None: + """ + Verify message interactions. + + This function is used to ensure that the consumer is able to handle the + messages that are defined in the Pact. The `handler` function is called + for each message in the Pact. + + The end-user is responsible for defining the `handler` function and + verifying that the messages are handled correctly. For example, if the + handler is meant to call an API, then the API call should be mocked out + and once the verification is complete, the mock should be verified. Any + exceptions raised by the handler will be caught and reported as + mismatches. + + Args: + handler: + The function that will be called for each message in the Pact. + + The first argument to the function is the message body, either as + a string or byte array. + + The second argument is the metadata for the message. If there + is no metadata, then this will be an empty dictionary. + + kind: + The type of message interaction. This must be one of `Async` + or `Sync`. + + raises: + Whether or not to raise an exception if the handler fails to + process a message. If set to `False`, then the function will + return a list of errors. + """ + errors: List[InteractionVerificationError] = [] + for message in self.interactions(kind): + request: pact.v3.ffi.MessageContents | None = None + if isinstance(message, pact.v3.ffi.SynchronousMessage): + request = message.request_contents + elif isinstance(message, pact.v3.ffi.AsynchronousMessage): + request = message.contents + else: + msg = f"Unknown message type: {type(message).__name__}" + raise TypeError(msg) + + if request is None: + warnings.warn( + f"Message '{message.description}' has no contents", + stacklevel=2, + ) + continue + + body = request.contents + metadata = {pair.key: pair.value for pair in request.metadata} + + try: + handler(body, metadata) + except Exception as e: # noqa: BLE001 + errors.append(InteractionVerificationError(message.description, e)) + + if raises: + if errors: + raise PactVerificationError(errors) + return None + return errors + def write_file( self, directory: Path | str | None = None, From 7f010c1aaff75289a56c59e3b6632c58c168f070 Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:50:48 +1000 Subject: [PATCH 22/23] chore(tests): implement v3/v4 consumer message compatibility suite Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- .../compatibility_suite/test_v3_consumer.py | 14 +- .../test_v3_message_consumer.py | 663 ++++++++++++++++++ .../compatibility_suite/test_v4_consumer.py | 21 +- .../test_v4_message_consumer.py | 148 ++++ tests/v3/compatibility_suite/util/__init__.py | 30 +- tests/v3/compatibility_suite/util/consumer.py | 39 +- 6 files changed, 891 insertions(+), 24 deletions(-) create mode 100644 tests/v3/compatibility_suite/test_v3_message_consumer.py create mode 100644 tests/v3/compatibility_suite/test_v4_message_consumer.py diff --git a/tests/v3/compatibility_suite/test_v3_consumer.py b/tests/v3/compatibility_suite/test_v3_consumer.py index b7014d6f7..f2f218a3f 100644 --- a/tests/v3/compatibility_suite/test_v3_consumer.py +++ b/tests/v3/compatibility_suite/test_v3_consumer.py @@ -10,7 +10,7 @@ from pytest_bdd import given, parsers, scenario, then from pact.v3.pact import HttpInteraction, Pact -from tests.v3.compatibility_suite.util import parse_markdown_table +from tests.v3.compatibility_suite.util import PactInteractionTuple, parse_markdown_table from tests.v3.compatibility_suite.util.consumer import ( the_pact_file_for_the_test_is_generated, ) @@ -48,21 +48,21 @@ def test_supports_data_for_provider_states() -> None: target_fixture="pact_interaction", ) def an_integration_is_being_defined_for_a_consumer_test() -> ( - Generator[tuple[Pact, HttpInteraction], Any, None] + Generator[PactInteractionTuple[HttpInteraction], Any, None] ): """An integration is being defined for a consumer test.""" pact = Pact("consumer", "provider") pact.with_specification("V3") - yield (pact, pact.upon_receiving("a request")) + yield PactInteractionTuple(pact, pact.upon_receiving("a request")) @given(parsers.re(r'a provider state "(?P[^"]+)" is specified')) def a_provider_state_is_specified( - pact_interaction: tuple[Pact, HttpInteraction], + pact_interaction: PactInteractionTuple[HttpInteraction], state: str, ) -> None: """A provider state is specified.""" - pact_interaction[1].given(state) + pact_interaction.interaction.given(state) @given( @@ -74,7 +74,7 @@ def a_provider_state_is_specified( converters={"table": parse_markdown_table}, ) def a_provider_state_is_specified_with_the_following_data( - pact_interaction: tuple[Pact, HttpInteraction], + pact_interaction: PactInteractionTuple[HttpInteraction], state: str, table: list[dict[str, Any]], ) -> None: @@ -93,7 +93,7 @@ def a_provider_state_is_specified_with_the_following_data( elif value.replace(".", "", 1).isdigit(): row[key] = float(value) - pact_interaction[1].given(state, parameters=table[0]) + pact_interaction.interaction.given(state, parameters=table[0]) ################################################################################ diff --git a/tests/v3/compatibility_suite/test_v3_message_consumer.py b/tests/v3/compatibility_suite/test_v3_message_consumer.py new file mode 100644 index 000000000..bdb038368 --- /dev/null +++ b/tests/v3/compatibility_suite/test_v3_message_consumer.py @@ -0,0 +1,663 @@ +"""V3 Message consumer feature tests.""" + +from __future__ import annotations + +import ast +import json +import logging +import re +from typing import TYPE_CHECKING, Any, List, NamedTuple + +from pytest_bdd import ( + given, + parsers, + scenario, + then, + when, +) + +from tests.v3.compatibility_suite.util import ( + FIXTURES_ROOT, + PactInteractionTuple, + parse_markdown_table, +) +from tests.v3.compatibility_suite.util.consumer import ( + a_message_integration_is_being_defined_for_a_consumer_test, +) + +if TYPE_CHECKING: + from pathlib import Path + + from pact.v3.pact import AsyncMessageInteraction, InteractionVerificationError + +logger = logging.getLogger(__name__) + +################################################################################ +## Helpers +################################################################################ + + +class ReceivedMessage(NamedTuple): + """Holder class for Message Received Payload.""" + + body: Any + context: Any + + +class PactResult(NamedTuple): + """Holder class for Pact Result objects.""" + + messages: List[ReceivedMessage] + pact_data: dict[str, Any] | None + errors: List[InteractionVerificationError] + + +def assert_type(expected_type: str, value: Any) -> None: # noqa: ANN401 + logger.debug("Ensuring that %s is of type %s", value, expected_type) + if expected_type == "integer": + assert value is not None + assert isinstance(value, int) or re.match(r"^\d+$", value) + else: + msg = f"Unknown type: {expected_type}" + raise ValueError(msg) + + +################################################################################ +## Scenarios +################################################################################ + + +@scenario( + "definition/features/V3/message_consumer.feature", + "When all messages are successfully processed", +) +def test_when_all_messages_are_successfully_processed() -> None: + """When all messages are successfully processed.""" + + +@scenario( + "definition/features/V3/message_consumer.feature", + "When not all messages are successfully processed", +) +def test_when_not_all_messages_are_successfully_processed() -> None: + """When not all messages are successfully processed.""" + + +@scenario( + "definition/features/V3/message_consumer.feature", + "Supports arbitrary message metadata", +) +def test_supports_arbitrary_message_metadata() -> None: + """Supports arbitrary message metadata.""" + + +@scenario( + "definition/features/V3/message_consumer.feature", + "Supports specifying provider states", +) +def test_supports_specifying_provider_states() -> None: + """Supports specifying provider states.""" + + +@scenario( + "definition/features/V3/message_consumer.feature", + "Supports data for provider states", +) +def test_supports_data_for_provider_states() -> None: + """Supports data for provider states.""" + + +@scenario( + "definition/features/V3/message_consumer.feature", + "Supports the use of generators with the message body", +) +def test_supports_the_use_of_generators_with_the_message_body() -> None: + """Supports the use of generators with the message body.""" + + +@scenario( + "definition/features/V3/message_consumer.feature", + "Supports the use of generators with message metadata", +) +def test_supports_the_use_of_generators_with_message_metadata() -> None: + """Supports the use of generators with message metadata.""" + + +################################################################################ +## Given +################################################################################ + + +a_message_integration_is_being_defined_for_a_consumer_test("V3") + + +@given( + parsers.re( + r'a provider state "(?P[^"]+)" for the message is specified' + r"( with the following data:\n)?(?P.*)", + re.DOTALL, + ), + converters={"table": lambda v: parse_markdown_table(v) if v else None}, +) +def a_provider_state_for_the_message_is_specified_with_the_following_data( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + state: str, + table: list[dict[str, str]] | None, +) -> None: + """A provider state for the message is specified with the following data.""" + logger.debug("Specifying provider state '%s': %s", state, table) + if table: + parameters = {k: ast.literal_eval(v) for k, v in table[0].items()} + pact_interaction.interaction.given(state, parameters=parameters) + else: + pact_interaction.interaction.given(state) + + +@given("a message is defined") +def a_message_is_defined() -> None: + """A message is defined.""" + + +@given( + parsers.re( + r"the message contains the following metadata:\n(?P
.+)", + re.DOTALL, + ), + converters={"table": parse_markdown_table}, +) +def the_message_contains_the_following_metadata( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + table: list[dict[str, Any]], +) -> None: + """The message contains the following metadata.""" + logger.debug("Adding metadata to message: %s", table) + for metadata in table: + if metadata.get("value", "").startswith("JSON: "): + metadata["value"] = metadata["value"].replace("JSON:", "") + pact_interaction.interaction.with_metadata({metadata["key"]: metadata["value"]}) + + +@given( + parsers.re( + r"the message is configured with the following:\n(?P
.+)", + re.DOTALL, + ), + converters={"table": parse_markdown_table}, +) +def the_message_is_configured_with_the_following( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + table: list[dict[str, Any]], +) -> None: + """The message is configured with the following.""" + assert len(table) == 1, "Only one row is expected" + config: dict[str, str] = table[0] + + if body := config.pop("body", None): + if body.startswith("file: "): + file = FIXTURES_ROOT / body.replace("file: ", "") + content_type = "application/json" if file.suffix == ".json" else None + pact_interaction.interaction.with_body(file.read_text(), content_type) + else: + msg = f"Unsupported body configuration: {config['body']}" + raise ValueError(msg) + + if generators := config.pop("generators", None): + if generators.startswith("JSON: "): + data = json.loads(generators.replace("JSON: ", "")) + pact_interaction.interaction.with_generators(data) + else: + file = FIXTURES_ROOT / generators + pact_interaction.interaction.with_generators(file.read_text()) + + if metadata := config.pop("metadata", None): + data = json.loads(metadata) + pact_interaction.interaction.with_metadata({ + k: json.dumps(v) for k, v in data.items() + }) + + if config: + msg = f"Unknown configuration keys: {', '.join(config.keys())}" + raise ValueError(msg) + + +@given( + parsers.re(r'the message payload contains the "(?P[^"]+)" JSON document') +) +def the_message_payload_contains_the_basic_json_document( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + basename: str, +) -> None: + """The message payload contains the "basic" JSON document.""" + json_path = FIXTURES_ROOT / f"{basename}.json" + if not json_path.is_file(): + msg = f"File not found: {json_path}" + raise FileNotFoundError(msg) + pact_interaction.interaction.with_body( + json_path.read_text(), + content_type="application/json", + ) + + +################################################################################ +## When +################################################################################ + + +@when("the message is successfully processed", target_fixture="pact_result") +def the_message_is_successfully_processed( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + temp_dir: Path, +) -> PactResult: + """The message is successfully processed.""" + messages: list[ReceivedMessage] = [] + + def handler( + body: str | bytes | None, + context: dict[str, str], + ) -> None: + messages.append(ReceivedMessage(body, context)) + + # While the expectation is that the message will be processed successfully, + # we don't raise an exception and instead capture any errors that occur. + errors = pact_interaction.pact.verify(handler, "Async", raises=False) + if errors: + logger.error("%d errors occured during verification:", len(errors)) + for error in errors: + logger.error(error) + msg = "Errors occurred during verification" + raise AssertionError(msg) + + (temp_dir / "pacts").mkdir(exist_ok=True, parents=True) + pact_interaction.pact.write_file(temp_dir / "pacts") + with (temp_dir / "pacts" / "consumer-provider.json").open() as file: + pact_data = json.load(file) + + return PactResult(messages, pact_data, errors) + + +@when( + parsers.re( + r"the message is NOT successfully processed " + r'with a "(?P[^"]+)" exception' + ), + target_fixture="pact_result", +) +def the_message_is_not_successfully_processed_with_an_exception( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + failure: str, +) -> PactResult: + """The message is NOT successfully processed with a "Test failed" exception.""" + messages: list[ReceivedMessage] = [] + + def handler(body: str | bytes | None, context: dict[str, str]) -> None: + messages.append(ReceivedMessage(body, context)) + raise AssertionError(failure) + + errors = pact_interaction.pact.verify(handler, "Async", raises=False) + return PactResult(messages, None, errors) + + +################################################################################ +## Then +################################################################################ + + +@then( + parsers.re( + r"a Pact file for the message interaction " + r"will(?P( NOT)?) have been written" + ), + converters={"success": lambda x: x != " NOT"}, +) +def a_pact_file_for_the_message_interaction_will_maybe_have_been_written( + temp_dir: Path, + success: bool, # noqa: FBT001 +) -> None: + """A Pact file for the message interaction will maybe have been written.""" + assert (temp_dir / "pacts" / "consumer-provider.json").exists() == success + + +@then(parsers.re(r'the consumer test error will be "(?P[^"]+)"')) +def the_consumer_test_error_will_be_test_failed( + pact_result: PactResult, + error: str, +) -> None: + """The consumer test error will be "Test failed".""" + assert len(pact_result.errors) == 1 + assert error in str(pact_result.errors[0].error) + + +@then( + parsers.re(r"the consumer test will have (?Ppassed|failed)"), + converters={"success": lambda x: x == "passed"}, +) +def the_consumer_test_will_have_passed_or_failed( + pact_result: PactResult, + success: bool, # noqa: FBT001 +) -> None: + """The consumer test will have passed or failed.""" + assert (len(pact_result.errors) == 0) == success + + +@then( + parsers.re( + r"the first message in the pact file content type " + r'will be "(?P[^"]+)"' + ) +) +def the_first_message_in_the_pact_file_content_type_will_be( + pact_result: PactResult, + content_type: str, +) -> None: + """The first message in the pact file content type will be.""" + if not pact_result.pact_data: + msg = "No pact data found" + raise RuntimeError(msg) + messages: list[dict[str, dict[str, Any]]] = pact_result.pact_data["messages"] + if not isinstance(messages, list) or not messages: + msg = "No messages found" + raise RuntimeError(msg) + assert messages[0].get("metadata", {}).get("contentType") == content_type + + +@then( + parsers.re( + r"the first message in the pact file will contain " + r"(?P\d+) provider states?" + ), + converters={"state_count": int}, +) +def the_first_message_in_the_pact_file_will_contain( + pact_result: PactResult, + state_count: int, +) -> None: + """The first message in the pact file will contain 1 provider state.""" + if not pact_result.pact_data: + msg = "No pact data found" + raise RuntimeError(msg) + messages: list[dict[str, list[Any]]] = pact_result.pact_data["messages"] + if not isinstance(messages, list) or not messages: + msg = "No messages found" + raise RuntimeError(msg) + assert len(messages[0].get("providerStates", [])) == state_count + + +@then( + parsers.re( + r"the first message in the Pact file will contain " + r'provider state "(?P[^"]+)"' + ) +) +def the_first_message_in_the_pact_file_will_contain_provider_state( + pact_result: PactResult, + state: str, +) -> None: + """The first message in the Pact file will contain provider state.""" + if not pact_result.pact_data: + msg = "No pact data found" + raise RuntimeError(msg) + messages = pact_result.pact_data["messages"] + if not isinstance(messages, list) or not messages: + msg = "No messages found" + raise RuntimeError(msg) + message: dict[str, Any] = messages[0] + provider_states: list[dict[str, Any]] = message.get("providerStates", []) + for provider_state in provider_states: + if provider_state["name"] == state: + break + else: + msg = f"Provider state not found: {state}" + raise AssertionError(msg) + + +@then( + parsers.re( + r"the first message in the pact file will contain " + r'the "(?P[^"]+)" document' + ) +) +def the_first_message_in_the_pact_file_will_contain_the_basic_json_document( + pact_result: PactResult, + basename: str, +) -> None: + """The first message in the pact file will contain the "basic.json" document.""" + path = FIXTURES_ROOT / basename + if not path.is_file(): + msg = f"File not found: {path}" + raise FileNotFoundError(msg) + if not pact_result.pact_data: + msg = "No pact data found" + raise RuntimeError(msg) + messages: list[dict[str, Any]] = pact_result.pact_data["messages"] + if not isinstance(messages, list) or not messages: + msg = "No messages found" + raise RuntimeError(msg) + try: + assert messages[0]["contents"] == json.loads(path.read_text()) + except json.JSONDecodeError as e: + logger.info("Error decoding JSON: %s", e) + logger.info("Performing basic string comparison") + assert messages[0]["contents"] == path.read_text() + + +@then( + parsers.re( + r"the first message in the pact file will contain " + r'the message metadata "(?P[^"]+)" == "(?P[^"\\]*(?:\\.[^"\\]*)*)"' + ) +) +def the_first_message_in_the_pact_file_will_contain_the_message_metadata( + pact_result: PactResult, + key: str, + value: Any, # noqa: ANN401 +) -> None: + """The first message in the pact file will contain the message metadata.""" + if value.startswith("JSON: "): + value = value.replace("JSON: ", "") + value = value.replace('\\"', '"') + value = json.loads(value) + if not pact_result.pact_data: + msg = "No pact data found" + raise RuntimeError(msg) + messages: list[dict[str, dict[str, Any]]] = pact_result.pact_data["messages"] + assert messages[0]["metadata"][key] == value + + +@then( + parsers.re( + r'the message contents for "(?P[^"]+)" ' + r'will have been replaced with an? "(?P[^"]+)"' + ) +) +def the_message_contents_will_have_been_replaced_with( + pact_result: PactResult, + path: str, + expected_type: str, +) -> None: + """The message contents for "$.one" will have been replaced with an "integer".""" + json_path = path.split(".") + assert len(json_path) == 2, "Only one level of nesting is supported" + assert json_path[0] == "$", "Only root level replacement is supported" + key = json_path[1] + + assert len(pact_result.messages) == 1 + message = pact_result.messages[0] + value = json.loads(message.body).get(key) + assert_type(expected_type, value) + + +@then( + parsers.parse( + "the pact file will contain {interaction_count:d} message interaction" + ) +) +def the_pact_file_will_contain_message_interaction( + pact_result: PactResult, + interaction_count: int, +) -> None: + """The pact file will contain N message interaction.""" + if not pact_result.pact_data: + msg = "No pact data found" + raise RuntimeError(msg) + messages: list[Any] = pact_result.pact_data["messages"] + assert len(messages) == interaction_count + + +@then( + parsers.re( + r'the provider state "(?P[^"]+)" for the message ' + r"will contain the following parameters:\n(?P
.+)", + re.DOTALL, + ), + converters={"table": parse_markdown_table}, +) +def the_provider_state_for_the_message_will_contain_the_following_parameters( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + pact_result: PactResult, + state: str, + table: list[dict[str, Any]], +) -> None: + """The provider state for the message will contain the following parameters.""" + assert len(table) == 1, "Only one row is expected" + expected = json.loads(table[0]["parameters"]) + + # It is unclear whether this test is meant to verify the `Interaction` + # object, or the result as written to the Pact file. As a result, we + # will perform both checks. + + ## Verifying the Pact File + + if not pact_result.pact_data: + msg = "No pact data found" + raise RuntimeError(msg) + messages: list[dict[str, list[dict[str, Any]]]] = pact_result.pact_data["messages"] + assert len(messages) == 1, "Only one message is expected" + message = messages[0] + + assert len(message["providerStates"]) > 0, "At least one provider state is expected" + provider_states = message["providerStates"] + for provider_state_dict in provider_states: + if provider_state_dict["name"] == state: + assert expected == provider_state_dict["params"] + break + else: + msg = f"Provider state not found in Pact file: {state}" + raise AssertionError(msg) + + ## Verifying the Interaction Object + + for interaction in pact_interaction.pact.interactions("Async"): + for provider_state in interaction.provider_states(): + if provider_state.name == state: + provider_state_params = { + k: ast.literal_eval(v) for k, v in provider_state.parameters() + } + assert expected == provider_state_params + break + else: + msg = f"Provider state not found: {state}" + raise ValueError(msg) + break + else: + msg = "No interactions found" + raise ValueError(msg) + + +@then( + parsers.re(r'the received message content type will be "(?P[^"]+)"') +) +def the_received_message_content_type_will_be( + pact_result: PactResult, + content_type: str, +) -> None: + """The received message content type will be "application/json".""" + assert len(pact_result.messages) == 1 + message = pact_result.messages[0] + assert message.context.get("contentType") == content_type + + +@then( + parsers.re( + r"the received message metadata will contain " + r'"(?P[^"]+)" == "(?P[^"\\]*(?:\\.[^"\\]*)*)"' + ) +) +def the_received_message_metadata_will_contain( + pact_result: PactResult, + key: str, + value: Any, # noqa: ANN401 +) -> None: + """The received message metadata will contain.""" + # If we're given some JSON value, we will need to parse the value from the + # `message.context` and compare it to the parsed JSON value; otherwise, + # equivalent JSON values may not match due to formatting differences. + json_matching = False + if value.startswith("JSON: "): + value = value.replace("JSON: ", "").replace(r"\"", '"') + value = json.loads(value) + json_matching = True + + assert len(pact_result.messages) == 1 + message = pact_result.messages[0] + for k, v in message.context.items(): + if k == key: + if json_matching: + assert json.loads(v) == value + else: + assert v == value + break + else: + msg = f"Key '{key}' not found in message metadata" + raise AssertionError(msg) + + +@then( + parsers.re( + r'the received message metadata will contain "(?P[^"]+)" ' + r'replaced with an? "(?P[^"]+)"' + ) +) +def the_received_message_metadata_will_contain_replaced_with( + pact_result: PactResult, + key: str, + expected_type: str, +) -> None: + """The received message metadata will contain "ID" replaced with an "integer".""" + assert isinstance(pact_result.messages, list) + assert len(pact_result.messages) == 1, "Only one message is expected" + message = pact_result.messages[0] + value = message.context.get(key) + assert_type(expected_type, value) + + +@then( + parsers.re( + r"the received message payload will contain " + r'the "(?P[^"]+)" JSON document' + ) +) +def the_received_message_payload_will_contain_the_basic_json_document( + pact_result: PactResult, + basename: str, +) -> None: + """The received message payload will contain the JSON document.""" + json_path = FIXTURES_ROOT / f"{basename}.json" + if not json_path.is_file(): + msg = f"File not found: {json_path}" + raise FileNotFoundError(msg) + + assert len(pact_result.messages) == 1 + message = pact_result.messages[0] + + try: + assert json.loads(message.body) == json.loads(json_path.read_text()) + except json.JSONDecodeError as e: + logger.info("Error decoding JSON: %s", e) + logger.info("Performing basic comparison") + if isinstance(message.body, str): + assert message.body == json_path.read_text() + elif isinstance(message.body, bytes): + assert message.body == json_path.read_bytes() + else: + msg = f"Unexpected message body type: {type(message.body).__name__}" + raise TypeError(msg) from None diff --git a/tests/v3/compatibility_suite/test_v4_consumer.py b/tests/v3/compatibility_suite/test_v4_consumer.py index 7b7ab019d..de70ea6f0 100644 --- a/tests/v3/compatibility_suite/test_v4_consumer.py +++ b/tests/v3/compatibility_suite/test_v4_consumer.py @@ -9,7 +9,7 @@ from pytest_bdd import given, parsers, scenario, then from pact.v3.pact import HttpInteraction, Pact -from tests.v3.compatibility_suite.util import string_to_int +from tests.v3.compatibility_suite.util import PactInteractionTuple, string_to_int from tests.v3.compatibility_suite.util.consumer import ( the_pact_file_for_the_test_is_generated, ) @@ -63,41 +63,38 @@ def test_supports_adding_comments() -> None: target_fixture="pact_interaction", ) def an_http_interaction_is_being_defined_for_a_consumer_test() -> ( - Generator[tuple[Pact, HttpInteraction], Any, None] + Generator[PactInteractionTuple[HttpInteraction], Any, None] ): """An HTTP interaction is being defined for a consumer test.""" pact = Pact("consumer", "provider") pact.with_specification("V4") - yield (pact, pact.upon_receiving("a request")) + yield PactInteractionTuple(pact, pact.upon_receiving("a request")) @given(parsers.re(r'a key of "(?P[^"]+)" is specified for the HTTP interaction')) def a_key_is_specified_for_the_http_interaction( - pact_interaction: tuple[Pact, HttpInteraction], + pact_interaction: PactInteractionTuple[HttpInteraction], key: str, ) -> None: """A key is specified for the HTTP interaction.""" - _, interaction = pact_interaction - interaction.set_key(key) + pact_interaction.interaction.set_key(key) @given("the HTTP interaction is marked as pending") def the_http_interaction_is_marked_as_pending( - pact_interaction: tuple[Pact, HttpInteraction], + pact_interaction: PactInteractionTuple[HttpInteraction], ) -> None: """The HTTP interaction is marked as pending.""" - _, interaction = pact_interaction - interaction.set_pending(pending=True) + pact_interaction.interaction.set_pending(pending=True) @given(parsers.re(r'a comment "(?P[^"]+)" is added to the HTTP interaction')) def a_comment_is_added_to_the_http_interaction( - pact_interaction: tuple[Pact, HttpInteraction], + pact_interaction: PactInteractionTuple[HttpInteraction], comment: str, ) -> None: """A comment of "" is added to the HTTP interaction.""" - _, interaction = pact_interaction - interaction.set_comment("text", [comment]) + pact_interaction.interaction.set_comment("text", [comment]) ################################################################################ diff --git a/tests/v3/compatibility_suite/test_v4_message_consumer.py b/tests/v3/compatibility_suite/test_v4_message_consumer.py new file mode 100644 index 000000000..5b1332165 --- /dev/null +++ b/tests/v3/compatibility_suite/test_v4_message_consumer.py @@ -0,0 +1,148 @@ +"""Message consumer feature tests.""" + +from __future__ import annotations + +import json +from typing import TYPE_CHECKING, Any + +from pytest_bdd import ( + given, + parsers, + scenario, + then, +) + +from tests.v3.compatibility_suite.util import PactInteractionTuple, string_to_int +from tests.v3.compatibility_suite.util.consumer import ( + a_message_integration_is_being_defined_for_a_consumer_test, + the_pact_file_for_the_test_is_generated, +) + +if TYPE_CHECKING: + from pact.v3.pact import AsyncMessageInteraction + + +@scenario( + "definition/features/V4/message_consumer.feature", + "Sets the type for the interaction", +) +def test_sets_the_type_for_the_interaction() -> None: + """Sets the type for the interaction.""" + + +@scenario( + "definition/features/V4/message_consumer.feature", + "Supports specifying a key for the interaction", +) +def test_supports_specifying_a_key_for_the_interaction() -> None: + """Supports specifying a key for the interaction.""" + + +@scenario( + "definition/features/V4/message_consumer.feature", + "Supports specifying the interaction is pending", +) +def test_supports_specifying_the_interaction_is_pending() -> None: + """Supports specifying the interaction is pending.""" + + +@scenario( + "definition/features/V4/message_consumer.feature", + "Supports adding comments", +) +def test_supports_adding_comments() -> None: + """Supports adding comments.""" + + +################################################################################ +## Given +################################################################################ + +a_message_integration_is_being_defined_for_a_consumer_test("V4") + + +@given( + parsers.re(r'a comment "(?P[^"]+)" is added to the message interaction') +) +def a_comment_is_added_to_the_message_interaction( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + comment: str, +) -> None: + """A comment "{comment}" is added to the message interaction.""" + pact_interaction.interaction.add_text_comment(comment) + + +@given( + parsers.re(r'a key of "(?P[^"]+)" is specified for the message interaction') +) +def a_key_is_specified_for_the_message_interaction( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], + key: str, +) -> None: + """A key is specified for the HTTP interaction.""" + pact_interaction.interaction.set_key(key) + + +@given("the message interaction is marked as pending") +def the_message_interaction_is_marked_as_pending( + pact_interaction: PactInteractionTuple[AsyncMessageInteraction], +) -> None: + """The message interaction is marked as pending.""" + pact_interaction.interaction.set_pending(pending=True) + + +################################################################################ +## When +################################################################################ + + +the_pact_file_for_the_test_is_generated() + + +################################################################################ +## Then +################################################################################ + + +@then( + parsers.re( + r"the (?P[^ ]+) interaction in the Pact file" + r" will have \"(?P[^\"]+)\" = '(?P[^']+)'" + ), + converters={"num": string_to_int}, +) +def the_interaction_in_the_pact_file_will_have_a_key_of( + pact_data: dict[str, Any], + num: int, + key: str, + value: str, +) -> None: + """The interaction in the Pact file will have a key of value.""" + assert "interactions" in pact_data + assert len(pact_data["interactions"]) >= num + interaction = pact_data["interactions"][num - 1] + assert key in interaction + value = json.loads(value) + if isinstance(value, list): + assert interaction[key] in value + else: + assert interaction[key] == value + + +@then( + parsers.re( + r"the (?P[^ ]+) interaction in the Pact file" + r' will have a type of "(?P[^"]+)"' + ), + converters={"num": string_to_int}, +) +def the_interaction_in_the_pact_file_will_container_provider_states( + pact_data: dict[str, Any], + num: int, + interaction_type: str, +) -> None: + """The interaction in the Pact file will container provider states.""" + assert "interactions" in pact_data + assert len(pact_data["interactions"]) >= num + interaction = pact_data["interactions"][num - 1] + assert interaction["type"] == interaction_type diff --git a/tests/v3/compatibility_suite/util/__init__.py b/tests/v3/compatibility_suite/util/__init__.py index 1351b1146..91b5871d1 100644 --- a/tests/v3/compatibility_suite/util/__init__.py +++ b/tests/v3/compatibility_suite/util/__init__.py @@ -31,7 +31,7 @@ def _(): from collections.abc import Collection, Mapping from datetime import date, datetime, time from pathlib import Path -from typing import Any +from typing import Any, Generic, TypeVar from xml.etree import ElementTree import flask @@ -47,6 +47,34 @@ def _(): SUITE_ROOT = Path(__file__).parent.parent / "definition" FIXTURES_ROOT = SUITE_ROOT / "fixtures" +_T = TypeVar("_T") + + +class PactInteractionTuple(Generic[_T]): + """ + Pact and interaction tuple. + + A number of steps in the compatibility suite require one or both of a `Pact` + and an `Interaction` subclass. This named tuple is used to pass these + objects around more easily. + + !!! note + + This should be simplified in the future to simply being a + [`NamedTuple`][typing.NamedTuple]; however, earlier versions of Python + do not support inheriting from multiple classes, thereby preventing + `class PactInteractionTuple(NamedTuple, Generic[_T])` (even if + [`Generic[_T]`][typing.Generic] serves no purpose other than to allow + type hinting). + """ + + def __init__(self, pact: Pact, interaction: _T) -> None: + """ + Instantiate the tuple. + """ + self.pact = pact + self.interaction = interaction + def string_to_int(word: str) -> int: """ diff --git a/tests/v3/compatibility_suite/util/consumer.py b/tests/v3/compatibility_suite/util/consumer.py index f9834187d..38bf7676e 100644 --- a/tests/v3/compatibility_suite/util/consumer.py +++ b/tests/v3/compatibility_suite/util/consumer.py @@ -11,12 +11,13 @@ import pytest import requests -from pytest_bdd import parsers, then, when +from pytest_bdd import given, parsers, then, when from yarl import URL from pact.v3 import Pact from tests.v3.compatibility_suite.util import ( FIXTURES_ROOT, + PactInteractionTuple, parse_markdown_table, string_to_int, truncate, @@ -26,11 +27,41 @@ from collections.abc import Generator from pathlib import Path - from pact.v3.pact import HttpInteraction, PactServer + from pact.v3.interaction._async_message_interaction import AsyncMessageInteraction + from pact.v3.pact import PactServer from tests.v3.compatibility_suite.util import InteractionDefinition logger = logging.getLogger(__name__) +################################################################################ +## Given +################################################################################ + + +def a_message_integration_is_being_defined_for_a_consumer_test( + version: str, + stacklevel: int = 1, +) -> None: + @given( + parsers.re( + r"a message (integration|interaction) " + r"is being defined for a consumer test" + ), + target_fixture="pact_interaction", + stacklevel=stacklevel + 1, + ) + def _() -> PactInteractionTuple[AsyncMessageInteraction]: + """ + A message integration is being defined for a consumer test. + """ + pact = Pact("consumer", "provider") + pact.with_specification(version) + return PactInteractionTuple( + pact, + pact.upon_receiving("an asynchronous message", "Async"), + ) + + ################################################################################ ## When ################################################################################ @@ -201,10 +232,10 @@ def the_pact_file_for_the_test_is_generated(stacklevel: int = 1) -> None: ) def _( temp_dir: Path, - pact_interaction: tuple[Pact, HttpInteraction], + pact_interaction: PactInteractionTuple[Any], ) -> dict[str, Any]: """The Pact file for the test is generated.""" - pact_interaction[0].write_file(temp_dir) + pact_interaction.pact.write_file(temp_dir) with (temp_dir / "consumer-provider.json").open("r") as file: return json.load(file) From ea7c23148a5fd60b4efe7ebd5235eb3c7f6a330f Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Mon, 24 Jun 2024 12:51:30 +1000 Subject: [PATCH 23/23] chore(examples): add v3 message consumer examples This change also includes a minor fix to ensure that the server is fully started before the test proceeds. Co-authored-by: valkolovos Co-authored-by: JP-Ellis Signed-off-by: JP-Ellis --- .gitignore | 3 + examples/docker-compose.yml | 4 +- examples/tests/test_01_provider_fastapi.py | 2 + examples/tests/test_01_provider_flask.py | 2 + examples/tests/v3/__init__.py | 0 examples/tests/v3/test_01_message_consumer.py | 170 ++++++++++++++++++ pyproject.toml | 2 + 7 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 examples/tests/v3/__init__.py create mode 100644 examples/tests/v3/test_01_message_consumer.py diff --git a/.gitignore b/.gitignore index 763596319..5ed2af480 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ src/pact/bin src/pact/data +# Test outputs +examples/tests/pacts + # Version is determined from the VCS src/pact/__version__.py diff --git a/examples/docker-compose.yml b/examples/docker-compose.yml index 2405a49a9..1b39fa089 100644 --- a/examples/docker-compose.yml +++ b/examples/docker-compose.yml @@ -15,7 +15,8 @@ services: broker: image: pactfoundation/pact-broker:latest-multi depends_on: - - postgres + postgres: + condition: service_healthy ports: - "9292:9292" restart: always @@ -41,3 +42,4 @@ services: interval: 1s timeout: 2s retries: 5 + start_period: 30s diff --git a/examples/tests/test_01_provider_fastapi.py b/examples/tests/test_01_provider_fastapi.py index 7ccd3128a..a95b5b5f8 100644 --- a/examples/tests/test_01_provider_fastapi.py +++ b/examples/tests/test_01_provider_fastapi.py @@ -24,6 +24,7 @@ from __future__ import annotations +import time from multiprocessing import Process from typing import Any, Dict, Generator, Union from unittest.mock import MagicMock @@ -93,6 +94,7 @@ def verifier() -> Generator[Verifier, Any, None]: provider_base_url=str(PROVIDER_URL), ) proc.start() + time.sleep(2) yield verifier proc.kill() diff --git a/examples/tests/test_01_provider_flask.py b/examples/tests/test_01_provider_flask.py index ba5c39d43..b7082dabb 100644 --- a/examples/tests/test_01_provider_flask.py +++ b/examples/tests/test_01_provider_flask.py @@ -24,6 +24,7 @@ from __future__ import annotations +import time from multiprocessing import Process from typing import Any, Dict, Generator, Union from unittest.mock import MagicMock @@ -81,6 +82,7 @@ def verifier() -> Generator[Verifier, Any, None]: provider_base_url=str(PROVIDER_URL), ) proc.start() + time.sleep(2) yield verifier proc.kill() diff --git a/examples/tests/v3/__init__.py b/examples/tests/v3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/tests/v3/test_01_message_consumer.py b/examples/tests/v3/test_01_message_consumer.py new file mode 100644 index 000000000..9016be9b8 --- /dev/null +++ b/examples/tests/v3/test_01_message_consumer.py @@ -0,0 +1,170 @@ +""" +Consumer test of example message handler using the v3 API. + +This test will create a pact between the message handler +and the message provider. +""" + +from __future__ import annotations + +import json +import logging +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generator, +) +from unittest.mock import MagicMock + +import pytest + +from examples.src.message import Handler +from pact.v3.pact import Pact + +if TYPE_CHECKING: + from collections.abc import Callable + + +log = logging.getLogger(__name__) + + +@pytest.fixture(scope="module") +def pact() -> Generator[Pact, None, None]: + """ + Set up Message Pact Consumer. + + This fixtures sets up the Message Pact consumer and the pact it has with a + provider. The consumer defines the expected messages it will receive from + the provider, and the Python test suite verifies that the correct actions + are taken. + + The verify method takes a function as an argument. This function + will be called with one or two arguments - the value of `with_body` and + the contents of `with_metadata` if provided. + + If the function under test does not take those parameters, you can create + a wrapper function to convert the pact parameters into the values + expected by your function. + + + For each interaction, the consumer defines the following: + + ```python + ( + pact = Pact("consumer name", "provider name") + processed_messages: list[MessagePact.MessagePactResult] = pact \ + .with_specification("V3") + .upon_receiving("a request", "Async") \ + .given("a request to write test.txt") \ + .with_body(msg) \ + .with_metadata({"Content-Type": "application/json"}) + .verify(pact_handler) + ) + + ``` + """ + pact_dir = Path(Path(__file__).parent.parent / "pacts") + pact = Pact("v3_message_consumer", "v3_message_provider") + log.info("Creating Message Pact with V3 specification") + yield pact.with_specification("V3") + pact.write_file(pact_dir, overwrite=True) + + +@pytest.fixture() +def handler() -> Handler: + """ + Fixture for the Handler. + + This fixture mocks the filesystem calls in the handler, so that we can + verify that the handler is calling the filesystem correctly. + """ + handler = Handler() + handler.fs = MagicMock() + handler.fs.write.return_value = None + handler.fs.read.return_value = "Hello world!" + return handler + + +@pytest.fixture() +def verifier( + handler: Handler, +) -> Generator[Callable[[str | bytes | None, Dict[str, Any]], None], Any, None]: + """ + Verifier function for the Pact. + + This function is passed to the `verify` method of the Pact object. It is + responsible for taking in the messages (along with the context/metadata) + and ensuring that the consumer is able to process the message correctly. + + In our case, we deserialize the message and pass it to the (pre-mocked) + handler for processing. We then verify that the underlying filesystem + calls were made as expected. + """ + assert isinstance(handler.fs, MagicMock), "Handler filesystem not mocked" + + def _verifier(msg: str | bytes | None, context: Dict[str, Any]) -> None: + assert msg is not None, "Message is None" + data = json.loads(msg) + log.info( + "Processing message: ", + extra={"input": msg, "processed_message": data, "context": context}, + ) + handler.process(data) + + yield _verifier + + assert handler.fs.mock_calls, "Handler did not call the filesystem" + + +def test_async_message_handler_write( + pact: Pact, + handler: Handler, + verifier: Callable[[str | bytes | None, Dict[str, Any]], None], +) -> None: + """ + Create a pact between the message handler and the message provider. + """ + assert isinstance(handler.fs, MagicMock), "Handler filesystem not mocked" + + ( + pact.upon_receiving("a write request", "Async") + .given("a request to write test.txt") + .with_body( + json.dumps({ + "action": "WRITE", + "path": "my_file.txt", + "contents": "Hello, world!", + }) + ) + ) + pact.verify(verifier, "Async") + + handler.fs.write.assert_called_once_with("my_file.txt", "Hello, world!") + + +def test_async_message_handler_read( + pact: Pact, + handler: Handler, + verifier: Callable[[str | bytes | None, Dict[str, Any]], None], +) -> None: + """ + Create a pact between the message handler and the message provider. + """ + assert isinstance(handler.fs, MagicMock), "Handler filesystem not mocked" + + ( + pact.upon_receiving("a read request", "Async") + .given("a request to read test.txt") + .with_body( + json.dumps({ + "action": "READ", + "path": "my_file.txt", + "contents": "Hello, world!", + }) + ) + ) + pact.verify(verifier, "Async") + + handler.fs.read.assert_called_once_with("my_file.txt") diff --git a/pyproject.toml b/pyproject.toml index a8ef954c2..7c90e1875 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -187,8 +187,10 @@ addopts = [ "--cov-report=xml", ] filterwarnings = [ + "ignore::DeprecationWarning:examples", "ignore::DeprecationWarning:pact", "ignore::DeprecationWarning:tests", + "ignore::PendingDeprecationWarning:examples", "ignore::PendingDeprecationWarning:pact", "ignore::PendingDeprecationWarning:tests", ]