Repository: pact-foundation/pact-python (language: python)

Issue:
Feature/add support and example messenger consumer using rust engine 380
<!--
Thank you for sending the PR! We appreciate you spending the time to work on these changes.
You can learn more about contributing to Pact-python here: https://github.com/pact-foundation/pact-python/blob/master/CONTRIBUTING.md

Happy contributing!
-->

## :airplane: Pre-flight checklist

-   [x] I have read the [Contributing Guidelines on pull requests](https://github.com/pact-foundation/pact-python/blob/master/CONTRIBUTING.md#pull-requests).
-   [x] **If this is a new API or substantial change**: the PR has an accompanying issue (closes #380) and the maintainers have approved on my working plan.

## :memo: Summary

Feature/add support and example messenger consumer using rust engine

## :fire: Motivation

Work for [Add support and example for a message consumer test using Rust engine #380](https://github.com/pact-foundation/pact-python/issues/380)

## :hammer: Test Plan

Implemented tests for the compatibility suite. Specifically message consumer tests for v3 and v4.

Add support and example for a message consumer test using Rust engine
“Add support” means adding DSL methods that call the underlying Rust functionality.

“Add example” means create an example Python project that showcases using the functionality.


Discussion / hints:
Hey @valkolovos do you want me to kick off the gh workflows for some feedback?


@YOU54F yes please!
Hey 👋

I'm back from leave and I'll start looking at both PRs over the next couple of days. It's awesome to have new contributors, and thank you for contributing such a major contribution!

As the PRs are quite large (a bit of 4k loc in all), I'll take my time reviewing them so that I can digest the changes, test things locally, and provide feedback. If it's alright, I might also add my own commits to the PRs for any minor fixes.

Feel free to reach out now that I'm back in case you have any quick questions (on Slack, or in comments here on GitHub).
I am currently working on this
Thanks @valkolovos 

Slack thread for ref:- https://pact-foundation.slack.com/archives/C9VECUP6E/p1715370769038429

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a3c15ea6fc..258d8ad7bf 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/.gitignore b/.gitignore
index 763596319e..5ed2af4808 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 2405a49a9a..1b39fa0898 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/src/message.py b/examples/src/message.py
index cab137ec0b..8157199034 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."""
diff --git a/hatch_build.py b/hatch_build.py
index 9842fb6a44..ab5e84e0f9 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/pyproject.toml b/pyproject.toml
index a8ef954c26..7c90e1875c 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",
 ]
diff --git a/src/pact/v3/__init__.py b/src/pact/v3/__init__.py
index 91329f2dce..15a207faa8 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"]
diff --git a/src/pact/v3/ffi.py b/src/pact/v3/ffi.py
index e23d94b86e..8108dcee44 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
 
@@ -89,7 +92,8 @@
 import typing
 import warnings
 from enum import Enum
-from typing import TYPE_CHECKING, Any, List
+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]
 
@@ -103,232 +107,396 @@
 
 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.
 
 
-class AsynchronousMessage: ...
-
+class AsynchronousMessage:
+    def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None:
+        """
+        Initialise a new Asynchronous Message.
 
-class Consumer: ...
+        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.
 
-class Generator: ...
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct AsynchronousMessage`.
+        """
+        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"
 
-class GeneratorCategoryIterator: ...
+    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)
 
-class GeneratorKeyValuePair: ...
+    @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)
 
-class HttpRequest: ...
+    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.
 
-class HttpResponse: ...
+        This may be `None` if the message has no contents.
+        """
+        return async_message_generate_contents(self)
 
 
-class InteractionHandle:
-    """
-    Handle to a HTTP Interaction.
+class Consumer: ...
 
-    [Rust
-    `InteractionHandle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/handles/struct.InteractionHandle.html)
-    """
 
-    def __init__(self, ref: int) -> None:
+class Generator:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Initialise a new Interaction Handle.
+        Initialise a generator value.
 
         Args:
-            ref:
-                Reference to the Interaction Handle.
+            ptr:
+                CFFI data structure.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct Generator`.
         """
-        self._ref: int = ref
+        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:
         """
-        String representation of the Interaction Handle.
+        Nice string representation.
         """
-        return f"InteractionHandle({self._ref})"
+        return "Generator"
 
     def __repr__(self) -> str:
         """
-        String representation of the Interaction Handle.
+        Debugging representation.
         """
-        return f"InteractionHandle({self._ref!r})"
-
-
-class MatchingRule: ...
-
-
-class MatchingRuleCategoryIterator: ...
-
-
-class MatchingRuleDefinitionResult: ...
-
-
-class MatchingRuleIterator: ...
-
-
-class MatchingRuleKeyValuePair: ...
+        return f"Generator({self._ptr!r})"
 
+    def __del__(self) -> None:
+        """
+        Destructor for the Generator.
+        """
 
-class MatchingRuleResult: ...
+    @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.
 
-class Message: ...
+        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 {}))
 
-class MessageContents: ...
+    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:
 
-class MessageHandle: ...
+                -   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 MessageMetadataIterator: ...
+class GeneratorCategoryIterator:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
+        """
+        Initialise a new generator category iterator.
 
+        Args:
+            ptr:
+                CFFI data structure.
 
-class MessageMetadataPair: ...
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct GeneratorCategoryIterator`.
+        """
+        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"
 
-class MessagePact: ...
+    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 MessagePactHandle: ...
+    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 MessagePactMessageIterator: ...
 
+class GeneratorKeyValuePair:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
+        """
+        Initialise a new key-value generator pair.
 
-class MessagePactMetadataIterator: ...
+        Args:
+            ptr:
+                CFFI data structure.
 
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct GeneratorKeyValuePair`.
+        """
+        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
 
-class MessagePactMetadataTriple: ...
+    def __str__(self) -> str:
+        """
+        Nice string representation.
+        """
+        return "GeneratorKeyValuePair"
 
+    def __repr__(self) -> str:
+        """
+        Debugging representation.
+        """
+        return f"GeneratorKeyValuePair({self._ptr!r})"
 
-class Mismatch: ...
+    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
 
-class Mismatches: ...
+    @property
+    def generator(self) -> Generator:
+        """
+        Generator value.
+        """
+        return Generator(self._ptr.generator)  # type: ignore[attr-defined]
 
 
-class MismatchesIterator: ...
+class HttpRequest: ...
 
 
-class Pact: ...
+class HttpResponse: ...
 
 
-class PactHandle:
+class InteractionHandle:
     """
-    Handle to a Pact.
+    Handle to a HTTP Interaction.
 
     [Rust
-    `PactHandle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/handles/struct.PactHandle.html)
+    `InteractionHandle`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/handles/struct.InteractionHandle.html)
     """
 
     def __init__(self, ref: int) -> None:
         """
-        Initialise a new Pact Handle.
+        Initialise a new Interaction Handle.
 
         Args:
             ref:
-                Rust library reference to the Pact Handle.
+                Reference to the Interaction Handle.
         """
         self._ref: int = ref
 
-    def __del__(self) -> None:
-        """
-        Destructor for the Pact Handle.
-        """
-        cleanup_plugins(self)
-        free_pact_handle(self)
-
     def __str__(self) -> str:
         """
-        String representation of the Pact Handle.
+        String representation of the Interaction Handle.
         """
-        return f"PactHandle({self._ref})"
+        return f"InteractionHandle({self._ref})"
 
     def __repr__(self) -> str:
         """
-        String representation of the Pact Handle.
+        String representation of the Interaction Handle.
         """
-        return f"PactHandle({self._ref!r})"
-
-
-class PactServerHandle:
-    """
-    Handle to a Pact Server.
-
-    This does not have an exact correspondance in the Rust library. It is used
-    to manage the lifecycle of the mock server.
-
-    # Implementation Notes
+        return f"InteractionHandle({self._ref!r})"
 
-    The Rust library uses the port number as a unique identifier, in much the
-    same was as it uses a wrapped integer for the Pact handle.
-    """
 
-    def __init__(self, ref: int) -> None:
+class MatchingRule:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Initialise a new Pact Server Handle.
+        Initialise a new key-value generator pair.
 
         Args:
-            ref:
-                Rust library reference to the Pact Server.
-        """
-        self._ref: int = ref
+            ptr:
+                CFFI data structure.
 
-    def __del__(self) -> None:
-        """
-        Destructor for the Pact Server Handle.
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct MatchingRule`.
         """
-        cleanup_mock_server(self)
+        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:
         """
-        String representation of the Pact Server Handle.
+        Nice string representation.
         """
-        return f"PactServerHandle({self._ref})"
+        return "MatchingRule"
 
     def __repr__(self) -> str:
         """
-        String representation of the Pact Server Handle.
+        Debugging representation.
         """
-        return f"PactServerHandle({self._ref!r})"
+        return f"MatchingRule({self._ptr!r})"
 
     @property
-    def port(self) -> int:
+    def json(self) -> dict[str, Any]:
         """
-        Port on which the Pact Server is running.
+        Dictionary representation of the matching rule.
         """
-        return self._ref
-
+        return json.loads(matching_rule_to_json(self))
 
-class PactInteraction: ...
-
-
-class PactInteractionIterator:
-    """
-    Iterator over a Pact's interactions.
-
-    Interactions encompasses all types of interactions, including HTTP
-    interactions and messages.
-    """
 
+class MatchingRuleCategoryIterator:
     def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Initialise a new Pact Interaction Iterator.
+        Initialise a new key-value generator pair.
 
         Args:
             ptr:
                 CFFI data structure.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct MatchingRuleCategoryIterator`.
         """
-        if ffi.typeof(ptr).cname != "struct PactInteractionIterator *":
+        if ffi.typeof(ptr).cname != "struct MatchingRuleCategoryIterator *":
             msg = (
-                "ptr must be a struct PactInteractionIterator, got"
+                "ptr must be a struct MatchingRuleCategoryIterator, got"
                 f" {ffi.typeof(ptr).cname}"
             )
             raise TypeError(msg)
@@ -338,43 +506,56 @@ def __str__(self) -> str:
         """
         Nice string representation.
         """
-        return "PactInteractionIterator"
+        return "MatchingRuleCategoryIterator"
 
     def __repr__(self) -> str:
         """
         Debugging representation.
         """
-        return f"PactInteractionIterator({self._ptr!r})"
+        return f"MatchingRuleCategoryIterator({self._ptr!r})"
 
     def __del__(self) -> None:
         """
-        Destructor for the Pact Interaction Iterator.
+        Destructor for the MatchingRuleCategoryIterator.
         """
-        pact_interaction_iter_delete(self)
+        matching_rules_iter_delete(self)
 
-    def __next__(self) -> PactInteraction:
+    def __iter__(self) -> Self:
         """
-        Get the next interaction from the iterator.
+        Return the iterator itself.
         """
-        return pact_interaction_iter_next(self)
+        return self
 
+    def __next__(self) -> MatchingRuleKeyValuePair:
+        """
+        Get the next generator category from the iterator.
+        """
+        return matching_rules_iter_next(self)
 
-class PactMessageIterator:
-    """
-    Iterator over a Pact's asynchronous messages.
-    """
 
+class MatchingRuleDefinitionResult: ...
+
+
+class MatchingRuleIterator: ...
+
+
+class MatchingRuleKeyValuePair:
     def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Initialise a new Pact Message Iterator.
+        Initialise a new key-value generator pair.
 
         Args:
             ptr:
                 CFFI data structure.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct MatchingRuleKeyValuePair`.
         """
-        if ffi.typeof(ptr).cname != "struct PactMessageIterator *":
+        if ffi.typeof(ptr).cname != "struct MatchingRuleKeyValuePair *":
             msg = (
-                f"ptr must be a struct PactMessageIterator, got {ffi.typeof(ptr).cname}"
+                "ptr must be a struct MatchingRuleKeyValuePair, got"
+                f" {ffi.typeof(ptr).cname}"
             )
             raise TypeError(msg)
         self._ptr = ptr
@@ -383,101 +564,148 @@ def __str__(self) -> str:
         """
         Nice string representation.
         """
-        return "PactMessageIterator"
+        return "MatchingRuleKeyValuePair"
 
     def __repr__(self) -> str:
         """
         Debugging representation.
         """
-        return f"PactMessageIterator({self._ptr!r})"
+        return f"MatchingRuleKeyValuePair({self._ptr!r})"
 
     def __del__(self) -> None:
         """
-        Destructor for the Pact Message Iterator.
+        Destructor for the MatchingRuleKeyValuePair.
         """
-        pact_message_iter_delete(self)
+        matching_rules_iter_pair_delete(self)
 
-    def __iter__(self) -> Self:
+    @property
+    def path(self) -> str:
         """
-        Return the iterator itself.
+        Matching Rule path.
         """
-        return self
+        s = ffi.string(self._ptr.path)  # type: ignore[attr-defined]
+        if isinstance(s, bytes):
+            s = s.decode("utf-8")
+        return s
 
-    def __next__(self) -> Message:
+    @property
+    def matching_rule(self) -> MatchingRule:
         """
-        Get the next message from the iterator.
+        Matching Rule value.
         """
-        return pact_message_iter_next(self)
+        return MatchingRule(self._ptr.matching_rule)  # type: ignore[attr-defined]
 
 
-class PactSyncHttpIterator:
-    """
-    Iterator over a Pact's synchronous HTTP interactions.
-    """
+class MatchingRuleResult: ...
 
-    def __init__(self, ptr: cffi.FFI.CData) -> None:
+
+class MessageContents:
+    def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = True) -> None:
         """
-        Initialise a new Pact Synchronous HTTP Iterator.
+        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.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct MessageContents`.
         """
-        if ffi.typeof(ptr).cname != "struct PactSyncHttpIterator *":
+        if ffi.typeof(ptr).cname != "struct MessageContents *":
             msg = (
-                "ptr must be a struct PactSyncHttpIterator, got"
-                f" {ffi.typeof(ptr).cname}"
+                "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 "PactSyncHttpIterator"
+        return "MessageContents"
 
     def __repr__(self) -> str:
         """
         Debugging representation.
         """
-        return f"PactSyncHttpIterator({self._ptr!r})"
+        return f"MessageContents({self._ptr!r})"
 
     def __del__(self) -> None:
         """
-        Destructor for the Pact Synchronous HTTP Iterator.
+        Destructor for the MessageContents.
         """
-        pact_sync_http_iter_delete(self)
+        if not self._owned:
+            message_contents_delete(self)
 
-    def __iter__(self) -> Self:
+    @property
+    def contents(self) -> str | bytes | None:
         """
-        Return the iterator itself.
+        Get the contents of the message.
         """
-        return self
+        return message_contents_get_contents_str(
+            self
+        ) or message_contents_get_contents_bin(self)
 
-    def __next__(self) -> SynchronousHttp:
+    @property
+    def metadata(self) -> GeneratorType[MessageMetadataPair, None, None]:
         """
-        Get the next message from the iterator.
+        Get the metadata for the message contents.
         """
-        return pact_sync_http_iter_next(self)
+        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
 
-class PactSyncMessageIterator:
+    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:
     """
-    Iterator over a Pact's synchronous messages.
+    Iterator over an interaction's metadata.
     """
 
     def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Initialise a new Pact Synchronous Message Iterator.
+        Initialise a new Message Metadata Iterator.
 
         Args:
             ptr:
                 CFFI data structure.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct MessageMetadataIterator`.
         """
-        if ffi.typeof(ptr).cname != "struct PactSyncMessageIterator *":
+        if ffi.typeof(ptr).cname != "struct MessageMetadataIterator *":
             msg = (
-                "ptr must be a struct PactSyncMessageIterator, got"
+                "ptr must be a struct MessageMetadataIterator, got"
                 f" {ffi.typeof(ptr).cname}"
             )
             raise TypeError(msg)
@@ -487,19 +715,19 @@ def __str__(self) -> str:
         """
         Nice string representation.
         """
-        return "PactSyncMessageIterator"
+        return "MessageMetadataIterator"
 
     def __repr__(self) -> str:
         """
         Debugging representation.
         """
-        return f"PactSyncMessageIterator({self._ptr!r})"
+        return f"MessageMetadataIterator({self._ptr!r})"
 
     def __del__(self) -> None:
         """
-        Destructor for the Pact Synchronous Message Iterator.
+        Destructor for the Pact Interaction Iterator.
         """
-        pact_sync_message_iter_delete(self)
+        message_metadata_iter_delete(self)
 
     def __iter__(self) -> Self:
         """
@@ -507,1723 +735,1651 @@ def __iter__(self) -> Self:
         """
         return self
 
-    def __next__(self) -> SynchronousMessage:
+    def __next__(self) -> MessageMetadataPair:
         """
-        Get the next message from the iterator.
+        Get the next interaction from the iterator.
         """
-        return pact_sync_message_iter_next(self)
+        return message_metadata_iter_next(self)
 
 
-class Provider: ...
+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.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct MessageMetadataPair`.
+        """
+        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})"
 
-class ProviderState: ...
+    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
 
-class ProviderStateIterator: ...
+    @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 ProviderStateParamIterator: ...
+class Mismatch: ...
 
 
-class ProviderStateParamPair: ...
+class Mismatches: ...
 
 
-class SynchronousHttp: ...
+class MismatchesIterator: ...
 
 
-class SynchronousMessage: ...
+class Pact: ...
 
 
-class VerifierHandle:
+class PactAsyncMessageIterator:
     """
-    Handle to a Verifier.
-
-    [Rust `VerifierHandle`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/verifier/handle/struct.VerifierHandle.html)
+    Iterator over a Pact's asynchronous messages.
     """
 
-    def __init__(self, ref: cffi.FFI.CData) -> None:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Initialise a new Verifier Handle.
+        Initialise a new Pact Asynchronous Message Iterator.
 
         Args:
-            ref:
-                Rust library reference to the Verifier Handle.
-        """
-        self._ref = ref
+            ptr:
+                CFFI data structure.
 
-    def __del__(self) -> None:
-        """
-        Destructor for the Verifier Handle.
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct PactAsyncMessageIterator`.
         """
-        verifier_shutdown(self)
+        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
 
     def __str__(self) -> str:
         """
-        String representation of the Verifier Handle.
+        Nice string representation.
         """
-        return f"VerifierHandle({hex(id(self._ref))})"
+        return "PactAsyncMessageIterator"
 
     def __repr__(self) -> str:
         """
-        String representation of the Verifier Handle.
+        Debugging representation.
         """
-        return f"<VerifierHandle: _ref at {hex(id(self._ref))}>"
-
-
-class ExpressionValueType(Enum):
-    """
-    Expression Value Type.
-
-    [Rust `ExpressionValueType`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/expressions/enum.ExpressionValueType.html)
-    """
+        return f"PactAsyncMessageIterator({self._ptr!r})"
 
-    UNKNOWN = lib.ExpressionValueType_Unknown
-    STRING = lib.ExpressionValueType_String
-    NUMBER = lib.ExpressionValueType_Number
-    INTEGER = lib.ExpressionValueType_Integer
-    DECIMAL = lib.ExpressionValueType_Decimal
-    BOOLEAN = lib.ExpressionValueType_Boolean
+    def __del__(self) -> None:
+        """
+        Destructor for the Pact Synchronous Message Iterator.
+        """
+        pact_async_message_iter_delete(self)
 
-    def __str__(self) -> str:
+    def __iter__(self) -> Self:
         """
-        Informal string representation of the Expression Value Type.
+        Return the iterator itself.
         """
-        return self.name
+        return self
 
-    def __repr__(self) -> str:
+    def __next__(self) -> AsynchronousMessage:
         """
-        Information-rich string representation of the Expression Value Type.
+        Get the next message from the iterator.
         """
-        return f"ExpressionValueType.{self.name}"
+        return pact_async_message_iter_next(self)
 
 
-class GeneratorCategory(Enum):
+class PactHandle:
     """
-    Generator Category.
+    Handle to a Pact.
 
-    [Rust `GeneratorCategory`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/generators/enum.GeneratorCategory.html)
+    [Rust
+    `PactHandle`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/handles/struct.PactHandle.html)
     """
 
-    METHOD = lib.GeneratorCategory_METHOD
-    PATH = lib.GeneratorCategory_PATH
-    HEADER = lib.GeneratorCategory_HEADER
-    QUERY = lib.GeneratorCategory_QUERY
-    BODY = lib.GeneratorCategory_BODY
-    STATUS = lib.GeneratorCategory_STATUS
-    METADATA = lib.GeneratorCategory_METADATA
+    def __init__(self, ref: int) -> None:
+        """
+        Initialise a new Pact Handle.
+
+        Args:
+            ref:
+                Rust library reference to the Pact Handle.
+        """
+        self._ref: int = ref
+
+    def __del__(self) -> None:
+        """
+        Destructor for the Pact Handle.
+        """
+        cleanup_plugins(self)
+        free_pact_handle(self)
 
     def __str__(self) -> str:
         """
-        Informal string representation of the Generator Category.
+        String representation of the Pact Handle.
         """
-        return self.name
+        return f"PactHandle({self._ref})"
 
     def __repr__(self) -> str:
         """
-        Information-rich string representation of the Generator Category.
+        String representation of the Pact Handle.
         """
-        return f"GeneratorCategory.{self.name}"
+        return f"PactHandle({self._ref!r})"
 
 
-class InteractionPart(Enum):
+class PactServerHandle:
     """
-    Interaction Part.
+    Handle to a Pact Server.
 
-    [Rust `InteractionPart`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/handles/enum.InteractionPart.html)
-    """
+    This does not have an exact correspondance in the Rust library. It is used
+    to manage the lifecycle of the mock server.
 
-    REQUEST = lib.InteractionPart_Request
-    RESPONSE = lib.InteractionPart_Response
+    # Implementation Notes
 
-    def __str__(self) -> str:
+    The Rust library uses the port number as a unique identifier, in much the
+    same was as it uses a wrapped integer for the Pact handle.
+    """
+
+    def __init__(self, ref: int) -> None:
         """
-        Informal string representation of the Interaction Part.
+        Initialise a new Pact Server Handle.
+
+        Args:
+            ref:
+                Rust library reference to the Pact Server.
         """
-        return self.name
+        self._ref: int = ref
 
-    def __repr__(self) -> str:
+    def __del__(self) -> None:
         """
-        Information-rich string representation of the Interaction Part.
+        Destructor for the Pact Server Handle.
         """
-        return f"InteractionPath.{self.name}"
-
-
-class LevelFilter(Enum):
-    """Level Filter."""
-
-    OFF = lib.LevelFilter_Off
-    ERROR = lib.LevelFilter_Error
-    WARN = lib.LevelFilter_Warn
-    INFO = lib.LevelFilter_Info
-    DEBUG = lib.LevelFilter_Debug
-    TRACE = lib.LevelFilter_Trace
+        cleanup_mock_server(self)
 
     def __str__(self) -> str:
         """
-        Informal string representation of the Level Filter.
+        String representation of the Pact Server Handle.
         """
-        return self.name
+        return f"PactServerHandle({self._ref})"
 
     def __repr__(self) -> str:
         """
-        Information-rich string representation of the Level Filter.
+        String representation of the Pact Server Handle.
         """
-        return f"LevelFilter.{self.name}"
+        return f"PactServerHandle({self._ref!r})"
 
+    @property
+    def port(self) -> int:
+        """
+        Port on which the Pact Server is running.
+        """
+        return self._ref
 
-class MatchingRuleCategory(Enum):
-    """
-    Matching Rule Category.
 
-    [Rust `MatchingRuleCategory`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/matching_rules/enum.MatchingRuleCategory.html)
+class PactInteraction: ...
+
+
+class PactInteractionIterator:
     """
+    Iterator over a Pact's interactions.
 
-    METHOD = lib.MatchingRuleCategory_METHOD
-    PATH = lib.MatchingRuleCategory_PATH
-    HEADER = lib.MatchingRuleCategory_HEADER
-    QUERY = lib.MatchingRuleCategory_QUERY
-    BODY = lib.MatchingRuleCategory_BODY
-    STATUS = lib.MatchingRuleCategory_STATUS
-    CONTENST = lib.MatchingRuleCategory_CONTENTS
-    METADATA = lib.MatchingRuleCategory_METADATA
+    Interactions encompasses all types of interactions, including HTTP
+    interactions and messages.
+    """
+
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
+        """
+        Initialise a new Pact Interaction Iterator.
+
+        Args:
+            ptr:
+                CFFI data structure.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct PactInteractionIterator`.
+        """
+        if ffi.typeof(ptr).cname != "struct PactInteractionIterator *":
+            msg = (
+                "ptr must be a struct PactInteractionIterator, got"
+                f" {ffi.typeof(ptr).cname}"
+            )
+            raise TypeError(msg)
+        self._ptr = ptr
 
     def __str__(self) -> str:
         """
-        Informal string representation of the Matching Rule Category.
+        Nice string representation.
         """
-        return self.name
+        return "PactInteractionIterator"
 
     def __repr__(self) -> str:
         """
-        Information-rich string representation of the Matching Rule Category.
+        Debugging representation.
         """
-        return f"MatchingRuleCategory.{self.name}"
+        return f"PactInteractionIterator({self._ptr!r})"
 
+    def __del__(self) -> None:
+        """
+        Destructor for the Pact Interaction Iterator.
+        """
+        pact_interaction_iter_delete(self)
 
-class PactSpecification(Enum):
-    """
-    Pact Specification.
+    def __next__(self) -> PactInteraction:
+        """
+        Get the next interaction from the iterator.
+        """
+        return pact_interaction_iter_next(self)
 
-    [Rust `PactSpecification`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/models/pact_specification/enum.PactSpecification.html)
-    """
 
-    UNKNOWN = lib.PactSpecification_Unknown
-    V1 = lib.PactSpecification_V1
-    V1_1 = lib.PactSpecification_V1_1
-    V2 = lib.PactSpecification_V2
-    V3 = lib.PactSpecification_V3
-    V4 = lib.PactSpecification_V4
+class PactSyncHttpIterator:
+    """
+    Iterator over a Pact's synchronous HTTP interactions.
+    """
 
-    @classmethod
-    def from_str(cls, version: str) -> PactSpecification:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Instantiate a Pact Specification from a string.
-
-        This method is case-insensitive, and allows for the version to be
-        specified with or without a leading "V", and with either a dot or an
-        underscore as the separator.
+        Initialise a new Pact Synchronous HTTP Iterator.
 
         Args:
-            version:
-                The version of the Pact Specification.
+            ptr:
+                CFFI data structure.
 
-        Returns:
-            The Pact Specification.
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct PactSyncHttpIterator`.
         """
-        version = version.upper().replace(".", "_")
-        if version.startswith("V"):
-            return cls[version]
-        return cls["V" + version]
+        if ffi.typeof(ptr).cname != "struct PactSyncHttpIterator *":
+            msg = (
+                "ptr must be a struct PactSyncHttpIterator, got"
+                f" {ffi.typeof(ptr).cname}"
+            )
+            raise TypeError(msg)
+        self._ptr = ptr
 
     def __str__(self) -> str:
         """
-        Informal string representation of the Pact Specification.
+        Nice string representation.
         """
-        return self.name
+        return "PactSyncHttpIterator"
 
     def __repr__(self) -> str:
         """
-        Information-rich string representation of the Pact Specification.
+        Debugging representation.
         """
-        return f"PactSpecification.{self.name}"
-
+        return f"PactSyncHttpIterator({self._ptr!r})"
 
-class StringResult:
-    """
-    String result.
-    """
+    def __del__(self) -> None:
+        """
+        Destructor for the Pact Synchronous HTTP Iterator.
+        """
+        pact_sync_http_iter_delete(self)
 
-    class _StringResult(Enum):
+    def __iter__(self) -> Self:
         """
-        Internal enum from Pact FFI.
+        Return the iterator itself.
+        """
+        return self
 
-        [Rust `StringResult`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/mock_server/enum.StringResult.html)
+    def __next__(self) -> SynchronousHttp:
         """
+        Get the next message from the iterator.
+        """
+        return pact_sync_http_iter_next(self)
 
-        FAILED = lib.StringResult_Failed
-        OK = lib.StringResult_Ok
 
-    class _StringResultCData:
-        tag: int
-        ok: cffi.FFI.CData
-        failed: cffi.FFI.CData
+class PactSyncMessageIterator:
+    """
+    Iterator over a Pact's synchronous messages.
+    """
 
-    def __init__(self, cdata: cffi.FFI.CData) -> None:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        Initialise a new String Result.
+        Initialise a new Pact Synchronous Message Iterator.
 
         Args:
-            cdata:
+            ptr:
                 CFFI data structure.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct PactSyncMessageIterator`.
         """
-        if ffi.typeof(cdata).cname != "struct StringResult":
-            msg = f"cdata must be a struct StringResult, got {ffi.typeof(cdata).cname}"
+        if ffi.typeof(ptr).cname != "struct PactSyncMessageIterator *":
+            msg = (
+                "ptr must be a struct PactSyncMessageIterator, got"
+                f" {ffi.typeof(ptr).cname}"
+            )
             raise TypeError(msg)
-        self._cdata = typing.cast(StringResult._StringResultCData, cdata)
+        self._ptr = ptr
 
     def __str__(self) -> str:
         """
-        String representation of the String Result.
+        Nice string representation.
         """
-        return self.text
+        return "PactSyncMessageIterator"
 
     def __repr__(self) -> str:
         """
-        Debugging string representation of the String Result.
+        Debugging representation.
         """
-        return f"<StringResult: {'OK' if self.is_ok else 'FAILED'}, {self.text!r}>"
+        return f"PactSyncMessageIterator({self._ptr!r})"
 
-    @property
-    def is_failed(self) -> bool:
+    def __del__(self) -> None:
         """
-        Whether the result is an error.
+        Destructor for the Pact Synchronous Message Iterator.
         """
-        return self._cdata.tag == StringResult._StringResult.FAILED.value
+        pact_sync_message_iter_delete(self)
 
-    @property
-    def is_ok(self) -> bool:
+    def __iter__(self) -> Self:
         """
-        Whether the result is ok.
+        Return the iterator itself.
         """
-        return self._cdata.tag == StringResult._StringResult.OK.value
+        return self
 
-    @property
-    def text(self) -> str:
+    def __next__(self) -> SynchronousMessage:
         """
-        The text of the result.
+        Get the next message from the iterator.
         """
-        # The specific `.ok` or `.failed` does not matter.
-        s = ffi.string(self._cdata.ok)
-        if isinstance(s, bytes):
-            return s.decode("utf-8")
-        return s
+        return pact_sync_message_iter_next(self)
 
-    def raise_exception(self) -> None:
-        """
-        Raise an exception with the text of the result.
 
-        Raises:
-            RuntimeError: If the result is an error.
+class Provider: ...
+
+
+class ProviderState:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
         """
-        if self.is_failed:
-            raise RuntimeError(self.text)
+        Initialise a new ProviderState.
 
+        Args:
+            ptr:
+                CFFI data structure.
 
-class OwnedString(str):
-    """
-    A string that owns its own memory.
+        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}"
+            raise TypeError(msg)
+        self._ptr = ptr
 
-    This is used to ensure that the memory is freed when the string is
-    destroyed.
+    def __str__(self) -> str:
+        """
+        Nice string representation.
+        """
+        return "ProviderState({self.name!r})"
 
-    As this is subclassed from `str`, it can be used in place of a normal string
-    in most cases.
-    """
+    def __repr__(self) -> str:
+        """
+        Debugging representation.
+        """
+        return f"ProviderState({self._ptr!r})"
 
-    __slots__ = ("_ptr", "_string")
+    @property
+    def name(self) -> str:
+        """
+        Provider State name.
+        """
+        return provider_state_get_name(self) or ""
 
-    def __new__(cls, ptr: cffi.FFI.CData) -> Self:
+    def parameters(self) -> GeneratorType[Tuple[str, str], None, None]:
         """
-        Create a new Owned String.
+        Provider State parameters.
 
-        As this is a subclass of the immutable type `str`, we need to override
-        the `__new__` method to ensure that the string is initialised correctly.
+        This is a generator that yields key-value pairs.
         """
-        s = ffi.string(ptr)
-        return super().__new__(cls, s if isinstance(s, str) else s.decode("utf-8"))
+        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 Owned String.
+        Initialise a new Provider State Iterator.
 
         Args:
             ptr:
                 CFFI data structure.
+
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct ProviderStateIterator`.
         """
+        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
-        s = ffi.string(ptr)
-        self._string = s if isinstance(s, str) else s.decode("utf-8")
 
     def __str__(self) -> str:
         """
-        String representation of the Owned String.
+        Nice string representation.
         """
-        return self._string
+        return "ProviderStateIterator"
 
     def __repr__(self) -> str:
         """
-        Debugging string representation of the Owned String.
+        Debugging representation.
         """
-        return f"<OwnedString: {self._string!r}, ptr={self._ptr!r}>"
+        return f"ProviderStateIterator({self._ptr!r})"
 
     def __del__(self) -> None:
         """
-        Destructor for the Owned String.
+        Destructor for the Provider State Iterator.
         """
-        string_delete(self)
+        provider_state_iter_delete(self)
 
-    def __eq__(self, other: object) -> bool:
+    def __iter__(self) -> ProviderStateIterator:
         """
-        Equality comparison.
-
-        Args:
-            other:
-                The object to compare to.
-
-        Returns:
-            Whether the two objects are equal.
+        Return the iterator itself.
         """
-        if isinstance(other, OwnedString):
-            return self._ptr == other._ptr
-        if isinstance(other, str):
-            return self._string == other
-        return super().__eq__(other)
-
+        return self
 
-def version() -> str:
-    """
-    Return the version of the pact_ffi library.
+    def __next__(self) -> ProviderState:
+        """
+        Get the next message from the iterator.
+        """
+        return provider_state_iter_next(self)
 
-    [Rust `pactffi_version`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_version)
 
-    Returns:
-        The version of the pact_ffi library as a string, in the form of `x.y.z`.
+class ProviderStateParamIterator:
     """
-    v = ffi.string(lib.pactffi_version())
-    if isinstance(v, bytes):
-        return v.decode("utf-8")
-    return v
-
-
-def init(log_env_var: str) -> None:
+    Iterator over a Provider States Parameters.
     """
-    Initialise the mock server library.
 
-    This can provide an environment variable name to use to set the log levels.
-    This function should only be called once, as it tries to install a global
-    tracing subscriber.
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
+        """
+        Initialise a new Provider State Param Iterator.
 
-    [Rust
-    `pactffi_init`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_init)
+        Args:
+            ptr:
+                CFFI data structure.
 
-    # Safety
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct ProviderStateParamIterator`.
+        """
+        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
 
-    log_env_var must be a valid NULL terminated UTF-8 string.
-    """
-    raise NotImplementedError
+    def __str__(self) -> str:
+        """
+        Nice string representation.
+        """
+        return "ProviderStateParamIterator"
 
+    def __repr__(self) -> str:
+        """
+        Debugging representation.
+        """
+        return f"ProviderStateParamIterator({self._ptr!r})"
 
-def init_with_log_level(level: str = "INFO") -> None:
-    """
-    Initialises logging, and sets the log level explicitly.
+    def __del__(self) -> None:
+        """
+        Destructor for the Provider State Param Iterator.
+        """
+        provider_state_param_iter_delete(self)
 
-    This function should only be called once, as it tries to install a global
-    tracing subscriber.
+    def __iter__(self) -> ProviderStateParamIterator:
+        """
+        Return the iterator itself.
+        """
+        return self
 
-    [Rust
-    `pactffi_init_with_log_level`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_init_with_log_level)
+    def __next__(self) -> ProviderStateParamPair:
+        """
+        Get the next message from the iterator.
+        """
+        return provider_state_param_iter_next(self)
 
-    Args:
-        level:
-            One of TRACE, DEBUG, INFO, WARN, ERROR, NONE/OFF. Case-insensitive.
 
-    # Safety
+class ProviderStateParamPair:
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
+        """
+        Initialise a new ProviderStateParamPair.
 
-    Exported functions are inherently unsafe.
-    """
-    raise NotImplementedError
+        Args:
+            ptr:
+                CFFI data structure.
 
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct ProviderStateParamPair`.
+        """
+        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
 
-def enable_ansi_support() -> None:
-    """
-    Enable ANSI coloured output on Windows.
+    def __str__(self) -> str:
+        """
+        Nice string representation.
+        """
+        return "ProviderStateParamPair"
 
-    On non-Windows platforms, this function is a no-op.
+    def __repr__(self) -> str:
+        """
+        Debugging representation.
+        """
+        return f"ProviderStateParamPair({self._ptr!r})"
 
-    [Rust
-    `pactffi_enable_ansi_support`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_enable_ansi_support)
+    def __del__(self) -> None:
+        """
+        Destructor for the Provider State Param Pair.
+        """
+        provider_state_param_pair_delete(self)
 
-    # Safety
+    @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
 
-    This function is safe.
-    """
-    raise NotImplementedError
+    @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
 
 
-def log_message(
-    message: str,
-    log_level: LevelFilter | str = LevelFilter.ERROR,
-    source: str | None = None,
-) -> None:
-    """
-    Log using the shared core logging facility.
+class SynchronousHttp:
+    def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None:
+        """
+        Initialise a new Synchronous HTTP Interaction.
 
-    [Rust
-    `pactffi_log_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_log_message)
+        Args:
+            ptr:
+                CFFI data structure.
 
-    This is useful for callers to have a single set of logs.
+            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.
 
-    Args:
-        message:
-            The contents written to the log
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct SynchronousHttp`.
+        """
+        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
 
-        log_level:
-            The verbosity at which this message should be logged.
+    def __str__(self) -> str:
+        """
+        Nice string representation.
+        """
+        return "SynchronousHttp"
 
-        source:
-            The source of the log, such as the class, module or caller.
-    """
-    if isinstance(log_level, str):
-        log_level = LevelFilter[log_level.upper()]
-    if source is None:
-        import inspect
+    def __repr__(self) -> str:
+        """
+        Debugging representation.
+        """
+        return f"SynchronousHttp({self._ptr!r})"
 
-        source = inspect.stack()[1].function
-    lib.pactffi_log_message(
-        source.encode("utf-8"),
-        log_level.name.encode("utf-8"),
-        message.encode("utf-8"),
-    )
+    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.
 
-def match_message(msg_1: Message, msg_2: Message) -> Mismatches:
-    """
-    Match a pair of messages, producing a collection of mismatches.
+        This needs to be unique in the pact file.
+        """
+        return sync_http_get_description(self)
 
-    If the messages match, the returned collection will be empty.
+    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
 
-    [Rust
-    `pactffi_match_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_match_message)
-    """
-    raise NotImplementedError
+    @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)
 
-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)
-    """
-    raise NotImplementedError
+class SynchronousMessage:
+    def __init__(self, ptr: cffi.FFI.CData, *, owned: bool = False) -> None:
+        """
+        Initialise a new Synchronous Message.
 
+        Args:
+            ptr:
+                CFFI data structure.
 
-def mismatches_delete(mismatches: Mismatches) -> None:
-    """
-    Delete mismatches.
+            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.
 
-    [Rust `pactffi_mismatches_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatches_delete)
-    """
-    raise NotImplementedError
+        Raises:
+            TypeError:
+                If the `ptr` is not a `struct SynchronousMessage`.
+        """
+        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 mismatches_iter_next(iter: MismatchesIterator) -> Mismatch:
-    """
-    Get the next mismatch from a mismatches iterator.
+    def __repr__(self) -> str:
+        """
+        Debugging representation.
+        """
+        return f"SynchronousMessage({self._ptr!r})"
 
-    [Rust `pactffi_mismatches_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatches_iter_next)
+    def __del__(self) -> None:
+        """
+        Destructor for the SynchronousMessage.
+        """
+        if not self._owned:
+            sync_message_delete(self)
 
-    Returns a null pointer if no mismatches remain.
-    """
-    raise NotImplementedError
+    @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 mismatches_iter_delete(iter: MismatchesIterator) -> None:
-    """
-    Delete a mismatches iterator when you're done with it.
+    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
 
-    [Rust `pactffi_mismatches_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatches_iter_delete)
-    """
-    raise NotImplementedError
+    @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
 
-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)
+class VerifierHandle:
     """
-    raise NotImplementedError
-
+    Handle to a Verifier.
 
-def mismatch_type(mismatch: Mismatch) -> str:
+    [Rust `VerifierHandle`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/verifier/handle/struct.VerifierHandle.html)
     """
-    Get the type of a mismatch.
 
-    [Rust `pactffi_mismatch_type`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatch_type)
-    """
-    raise NotImplementedError
+    def __init__(self, ref: cffi.FFI.CData) -> None:
+        """
+        Initialise a new Verifier Handle.
 
+        Args:
+            ref:
+                Rust library reference to the Verifier Handle.
+        """
+        self._ref = ref
 
-def mismatch_summary(mismatch: Mismatch) -> str:
-    """
-    Get a summary of a mismatch.
+    def __del__(self) -> None:
+        """
+        Destructor for the Verifier Handle.
+        """
+        verifier_shutdown(self)
 
-    [Rust `pactffi_mismatch_summary`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_mismatch_summary)
-    """
-    raise NotImplementedError
-
-
-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)
-    """
-    raise NotImplementedError
-
-
-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)
-    """
-    raise NotImplementedError
-
-
-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)
-
-    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
-    get more context on why the error happened.
-
-    Do note that this error-reporting mechanism only reports the top-level error
-    message, not any source information embedded in the original Rust error
-    type. If you want more detailed information for debugging purposes, use the
-    logging interface.
-
-    Args:
-        length:
-            The length of the buffer to allocate for the error message. If the
-            error message is longer than this, it will be truncated.
-
-    Returns:
-        A string containing the error message, or None if there is no error
-        message.
-
-    Raises:
-        RuntimeError: If the error message could not be retrieved.
-    """
-    buffer = ffi.new("char[]", length)
-    ret: int = lib.pactffi_get_error_message(buffer, length)
-
-    if ret >= 0:
-        # While the documentation says that the return value is the number of bytes
-        # written, the actually return value is always 0 on success.
-        if msg := ffi.string(buffer):
-            if isinstance(msg, bytes):
-                return msg.decode("utf-8")
-            return msg
-        return None
-    if ret == -1:
-        msg = "The provided buffer is a null pointer."
-    elif ret == -2:  # noqa: PLR2004
-        # Instead of returning an error here, we call the function again with a
-        # larger buffer.
-        return get_error_message(length * 32)
-    elif ret == -3:  # noqa: PLR2004
-        msg = "The write failed for some other reason."
-    elif ret == -4:  # noqa: PLR2004
-        msg = "The error message had an interior NULL."
-    else:
-        msg = "An unknown error occurred."
-    raise RuntimeError(msg)
-
-
-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)
-    """
-    raise NotImplementedError
-
-
-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)
-
-    Args:
-        level_filter:
-            The level of logs to filter to. If a string is given, it must match
-            one of the [`LevelFilter`][pact.v3.ffi.LevelFilter] values (case
-            insensitive).
-
-    Raises:
-        RuntimeError: If there was an error setting the logger.
-    """
-    if isinstance(level_filter, str):
-        level_filter = LevelFilter[level_filter.upper()]
-    ret: int = lib.pactffi_log_to_stderr(level_filter.value)
-    if ret != 0:
-        msg = "There was an unknown error setting the logger."
-        raise RuntimeError(msg)
-
-
-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)
-
-    # Safety
-
-    This function will fail if the file_name pointer is invalid or does not
-    point to a NULL terminated string.
-    """
-    raise NotImplementedError
-
-
-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)
-    """
-    if isinstance(level_filter, str):
-        level_filter = LevelFilter[level_filter.upper()]
-    ret: int = lib.pactffi_log_to_buffer(level_filter.value)
-    if ret != 0:
-        msg = "There was an unknown error setting the logger."
-        raise RuntimeError(msg)
-
-
-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)
-
-    This initialized logger does nothing until `pactffi_logger_apply` has been called.
-
-    # Usage
-
-    ```c
-    pactffi_logger_init();
-    ```
-
-    # Safety
-
-    This function is always safe to call.
-    """
-    raise NotImplementedError
-
-
-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)
-
-    This logger does nothing until `pactffi_logger_apply` has been called.
-
-    Types of sinks can be specified:
-
-    - stdout (`pactffi_logger_attach_sink("stdout", LevelFilter_Info)`)
-    - stderr (`pactffi_logger_attach_sink("stderr", LevelFilter_Debug)`)
-    - file w/ file path (`pactffi_logger_attach_sink("file /some/file/path",
-      LevelFilter_Trace)`)
-    - buffer (`pactffi_logger_attach_sink("buffer", LevelFilter_Debug)`)
-
-    # Usage
-
-    ```c
-    int result = pactffi_logger_attach_sink("file /some/file/path", LogLevel_Filter);
-    ```
-
-    # Error Handling
-
-    The return error codes are as follows:
-
-    - `-1`: Can't set logger (applying the logger failed, perhaps because one is
-      applied already).
-    - `-2`: No logger has been initialized (call `pactffi_logger_init` before
-      any other log function).
-    - `-3`: The sink specifier was not UTF-8 encoded.
-    - `-4`: The sink type specified is not a known type (known types: "stdout",
-      "stderr", or "file /some/path").
-    - `-5`: No file path was specified in a file-type sink specification.
-    - `-6`: Opening a sink to the specified file path failed (check
-      permissions).
-
-    # Safety
-
-    This function checks the validity of the passed-in sink specifier, and
-    errors out if the specifier isn't valid UTF-8. Passing in an invalid or NULL
-    pointer will result in undefined behaviour.
-    """
-    raise NotImplementedError
-
-
-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)
-
-    If no sinks have been setup, will set the log level to info and the target
-    to standard out.
-
-    This function will install a global tracing subscriber. Any attempts to
-    modify the logger after the call to `logger_apply` will fail.
-    """
-    raise NotImplementedError
-
-
-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)
-
-    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
-    with `pactffi_string_delete`.
-
-    Fetches the logs associated with the provided identifier, or uses the
-    "global" one if the identifier is not specified (i.e. NULL).
-
-    Returns a NULL pointer if the buffer can't be fetched. This can occur is
-    there is not sufficient memory to make a copy of the contents or the buffer
-    contains non-UTF-8 characters.
-
-    # Safety
-
-    This function will fail if the log_id pointer is invalid or does not point
-    to a NULL terminated string.
-    """
-    raise NotImplementedError
-
-
-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)
-
-    The returned Pact model must be freed with the `pactffi_pact_model_delete`
-    function when no longer needed.
-
-    # Error Handling
-
-    This function will return a NULL pointer if passed a NULL pointer or if an
-    error occurs.
-    """
-    raise NotImplementedError
-
-
-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)
-    """
-    raise NotImplementedError
-
-
-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)
-
-    The iterator will have to be deleted using the
-    `pactffi_pact_interaction_iter_delete` function. The iterator will contain a
-    copy of the interactions, so it will not be affected but mutations to the
-    Pact model and will still function if the Pact model is deleted.
-
-    # Safety This function is safe as long as the Pact pointer is a valid
-    pointer.
-
-    # Errors On any error, this function will return a NULL pointer.
-    """
-    raise NotImplementedError
-
-
-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)
-    """
-    raise NotImplementedError
-
-
-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)
-    """
-    raise NotImplementedError
-
-
-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)
-
-    # Safety
-
-    This function is safe.
-
-    # Error Handling
-
-    Returns NULL on error.
-    """
-    raise NotImplementedError
-
-
-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)
-    """
-    raise NotImplementedError
-
-
-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)
-
-    # Safety
+    def __str__(self) -> str:
+        """
+        String representation of the Verifier Handle.
+        """
+        return f"VerifierHandle({hex(id(self._ref))})"
 
-    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.
+    def __repr__(self) -> str:
+        """
+        String representation of the Verifier Handle.
+        """
+        return f"<VerifierHandle: _ref at {hex(id(self._ref))}>"
 
-    # Error Handling
 
-    If the message is NULL, returns NULL.
+class ExpressionValueType(Enum):
     """
-    raise NotImplementedError
-
+    Expression Value Type.
 
-def async_message_get_contents_str(message: AsynchronousMessage) -> str:
+    [Rust `ExpressionValueType`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/expressions/enum.ExpressionValueType.html)
     """
-    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)
 
-    # Safety
+    UNKNOWN = lib.ExpressionValueType_Unknown
+    STRING = lib.ExpressionValueType_String
+    NUMBER = lib.ExpressionValueType_Number
+    INTEGER = lib.ExpressionValueType_Integer
+    DECIMAL = lib.ExpressionValueType_Decimal
+    BOOLEAN = lib.ExpressionValueType_Boolean
 
-    The returned string must be deleted with `pactffi_string_delete`.
+    def __str__(self) -> str:
+        """
+        Informal string representation of the Expression Value Type.
+        """
+        return self.name
 
-    The returned string can outlive the message.
+    def __repr__(self) -> str:
+        """
+        Information-rich string representation of the Expression Value Type.
+        """
+        return f"ExpressionValueType.{self.name}"
 
-    # 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.
+class GeneratorCategory(Enum):
     """
-    raise NotImplementedError
-
+    Generator Category.
 
-def async_message_set_contents_str(
-    message: AsynchronousMessage,
-    contents: str,
-    content_type: str,
-) -> None:
+    [Rust `GeneratorCategory`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/generators/enum.GeneratorCategory.html)
     """
-    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)
 
-    - `message` - the message to set the contents for
-    - `contents` - pointer to contents to copy from. Must be a valid
-      NULL-terminated UTF-8 string pointer.
-    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
-      the content type of the data.
+    METHOD = lib.GeneratorCategory_METHOD
+    PATH = lib.GeneratorCategory_PATH
+    HEADER = lib.GeneratorCategory_HEADER
+    QUERY = lib.GeneratorCategory_QUERY
+    BODY = lib.GeneratorCategory_BODY
+    STATUS = lib.GeneratorCategory_STATUS
+    METADATA = lib.GeneratorCategory_METADATA
 
-    # Safety
+    def __str__(self) -> str:
+        """
+        Informal string representation of the Generator Category.
+        """
+        return self.name
 
-    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.
+    def __repr__(self) -> str:
+        """
+        Information-rich string representation of the Generator Category.
+        """
+        return f"GeneratorCategory.{self.name}"
 
-    # 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.
+class InteractionPart(Enum):
     """
-    raise NotImplementedError
-
+    Interaction Part.
 
-def async_message_get_contents_length(message: AsynchronousMessage) -> int:
+    [Rust `InteractionPart`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/handles/enum.InteractionPart.html)
     """
-    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)
-
-    # Safety
-
-    This function is safe.
+    REQUEST = lib.InteractionPart_Request
+    RESPONSE = lib.InteractionPart_Response
 
-    # Error Handling
+    def __str__(self) -> str:
+        """
+        Informal string representation of the Interaction Part.
+        """
+        return self.name
 
-    If the message is NULL, returns 0. If the body of the request is missing,
-    then this function also returns 0.
-    """
-    raise NotImplementedError
+    def __repr__(self) -> str:
+        """
+        Information-rich string representation of the Interaction Part.
+        """
+        return f"InteractionPath.{self.name}"
 
 
-def async_message_get_contents_bin(message: AsynchronousMessage) -> str:
-    """
-    Get the contents of an `AsynchronousMessage` as bytes.
+class LevelFilter(Enum):
+    """Level Filter."""
 
-    [Rust
-    `pactffi_async_message_get_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_contents_bin)
+    OFF = lib.LevelFilter_Off
+    ERROR = lib.LevelFilter_Error
+    WARN = lib.LevelFilter_Warn
+    INFO = lib.LevelFilter_Info
+    DEBUG = lib.LevelFilter_Debug
+    TRACE = lib.LevelFilter_Trace
 
-    # Safety
+    def __str__(self) -> str:
+        """
+        Informal string representation of the Level Filter.
+        """
+        return self.name
 
-    The number of bytes in the buffer will be returned by
-    `pactffi_async_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.
+    def __repr__(self) -> str:
+        """
+        Information-rich string representation of the Level Filter.
+        """
+        return f"LevelFilter.{self.name}"
 
-    # Error Handling
 
-    If the message is NULL, returns NULL. If the body of the message is missing,
-    then this function also returns NULL.
+class MatchingRuleCategory(Enum):
     """
-    raise NotImplementedError
-
+    Matching Rule Category.
 
-def async_message_set_contents_bin(
-    message: AsynchronousMessage,
-    contents: str,
-    len: int,
-    content_type: str,
-) -> None:
+    [Rust `MatchingRuleCategory`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/matching_rules/enum.MatchingRuleCategory.html)
     """
-    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)
+    METHOD = lib.MatchingRuleCategory_METHOD
+    PATH = lib.MatchingRuleCategory_PATH
+    HEADER = lib.MatchingRuleCategory_HEADER
+    QUERY = lib.MatchingRuleCategory_QUERY
+    BODY = lib.MatchingRuleCategory_BODY
+    STATUS = lib.MatchingRuleCategory_STATUS
+    CONTENST = lib.MatchingRuleCategory_CONTENTS
+    METADATA = lib.MatchingRuleCategory_METADATA
 
-    * `message` - the message to set the contents for
-    * `contents` - pointer to contents to copy from
-    * `len` - number of bytes to copy from the contents pointer
-    * `content_type` - pointer to the NULL-terminated UTF-8 string containing
-      the content type of the data.
+    def __str__(self) -> str:
+        """
+        Informal string representation of the Matching Rule Category.
+        """
+        return self.name
 
-    # Safety
+    def __repr__(self) -> str:
+        """
+        Information-rich string representation of the Matching Rule Category.
+        """
+        return f"MatchingRuleCategory.{self.name}"
 
-    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
+class PactSpecification(Enum):
+    """
+    Pact Specification.
 
-    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.
+    [Rust `PactSpecification`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/models/pact_specification/enum.PactSpecification.html)
     """
-    raise NotImplementedError
 
+    UNKNOWN = lib.PactSpecification_Unknown
+    V1 = lib.PactSpecification_V1
+    V1_1 = lib.PactSpecification_V1_1
+    V2 = lib.PactSpecification_V2
+    V3 = lib.PactSpecification_V3
+    V4 = lib.PactSpecification_V4
 
-def async_message_get_description(message: AsynchronousMessage) -> str:
-    r"""
-    Get a copy of the description.
+    @classmethod
+    def from_str(cls, version: str) -> PactSpecification:
+        """
+        Instantiate a Pact Specification from a string.
 
-    [Rust
-    `pactffi_async_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_description)
+        This method is case-insensitive, and allows for the version to be
+        specified with or without a leading "V", and with either a dot or an
+        underscore as the separator.
 
-    # Safety
+        Args:
+            version:
+                The version of the Pact Specification.
 
-    The returned string must be deleted with `pactffi_string_delete`.
+        Returns:
+            The Pact Specification.
+        """
+        version = version.upper().replace(".", "_")
+        if version.startswith("V"):
+            return cls[version]
+        return cls["V" + version]
 
-    Since it is a copy, the returned string may safely outlive the
-    `AsynchronousMessage`.
+    def __str__(self) -> str:
+        """
+        Informal string representation of the Pact Specification.
+        """
+        return self.name
 
-    # Errors
+    def __repr__(self) -> str:
+        """
+        Information-rich string representation of the Pact Specification.
+        """
+        return f"PactSpecification.{self.name}"
 
-    On failure, this function will return a NULL pointer.
 
-    This function may fail if the Rust string contains embedded null ('\0')
-    bytes.
+class StringResult:
+    """
+    String result.
     """
-    raise NotImplementedError
 
+    class _StringResult(Enum):
+        """
+        Internal enum from Pact FFI.
 
-def async_message_set_description(
-    message: AsynchronousMessage,
-    description: str,
-) -> int:
-    """
-    Write the `description` field on the `AsynchronousMessage`.
+        [Rust `StringResult`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/mock_server/enum.StringResult.html)
+        """
 
-    [Rust `pactffi_async_message_set_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_set_description)
+        FAILED = lib.StringResult_Failed
+        OK = lib.StringResult_Ok
 
-    # Safety
+    class _StringResultCData:
+        tag: int
+        ok: cffi.FFI.CData
+        failed: cffi.FFI.CData
 
-    `description` must contain valid UTF-8. Invalid UTF-8
-    will be replaced with U+FFFD REPLACEMENT CHARACTER.
+    def __init__(self, cdata: cffi.FFI.CData) -> None:
+        """
+        Initialise a new String Result.
 
-    This function will only reallocate if the new string
-    does not fit in the existing buffer.
+        Args:
+            cdata:
+                CFFI data structure.
 
-    # Error Handling
+        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}"
+            raise TypeError(msg)
+        self._cdata = typing.cast(StringResult._StringResultCData, cdata)
 
-    Errors will be reported with a non-zero return value.
-    """
-    raise NotImplementedError
+    def __str__(self) -> str:
+        """
+        String representation of the String Result.
+        """
+        return self.text
 
+    def __repr__(self) -> str:
+        """
+        Debugging string representation of the String Result.
+        """
+        return f"<StringResult: {'OK' if self.is_ok else 'FAILED'}, {self.text!r}>"
 
-def async_message_get_provider_state(
-    message: AsynchronousMessage,
-    index: int,
-) -> ProviderState:
-    r"""
-    Get a copy of the provider state at the given index from this message.
+    @property
+    def is_failed(self) -> bool:
+        """
+        Whether the result is an error.
+        """
+        return self._cdata.tag == StringResult._StringResult.FAILED.value
 
-    [Rust
-    `pactffi_async_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_async_message_get_provider_state)
+    @property
+    def is_ok(self) -> bool:
+        """
+        Whether the result is ok.
+        """
+        return self._cdata.tag == StringResult._StringResult.OK.value
 
-    # Safety
+    @property
+    def text(self) -> str:
+        """
+        The text of the result.
+        """
+        # The specific `.ok` or `.failed` does not matter.
+        s = ffi.string(self._cdata.ok)
+        if isinstance(s, bytes):
+            return s.decode("utf-8")
+        return s
 
-    The returned structure must be deleted with `provider_state_delete`.
+    def raise_exception(self) -> None:
+        """
+        Raise an exception with the text of the result.
 
-    Since it is a copy, the returned structure may safely outlive the
-    `AsynchronousMessage`.
+        Raises:
+            RuntimeError:
+                If the result is an error.
 
-    # Error Handling
+        Raises:
+            RuntimeError:
+                If the result is an error.
+        """
+        if self.is_failed:
+            raise RuntimeError(self.text)
 
-    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.
+class OwnedString(str):
     """
-    raise NotImplementedError
+    A string that owns its own memory.
 
+    This is used to ensure that the memory is freed when the string is
+    destroyed.
 
-def async_message_get_provider_state_iter(
-    message: AsynchronousMessage,
-) -> ProviderStateIterator:
+    As this is subclassed from `str`, it can be used in place of a normal string
+    in most cases.
     """
-    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)
-
-    # Safety
 
-    The underlying data must not change during iteration.
-
-    # Error Handling
+    __slots__ = ("_ptr", "_string")
 
-    Returns NULL if an error occurs.
-    """
-    raise NotImplementedError
+    def __new__(cls, ptr: cffi.FFI.CData) -> Self:
+        """
+        Create a new Owned String.
 
+        As this is a subclass of the immutable type `str`, we need to override
+        the `__new__` method to ensure that the string is initialised correctly.
+        """
+        s = ffi.string(ptr)
+        return super().__new__(cls, s if isinstance(s, str) else s.decode("utf-8"))
 
-def consumer_get_name(consumer: Consumer) -> str:
-    r"""
-    Get a copy of this consumer's name.
+    def __init__(self, ptr: cffi.FFI.CData) -> None:
+        """
+        Initialise a new Owned String.
 
-    [Rust `pactffi_consumer_get_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_consumer_get_name)
+        Args:
+            ptr:
+                CFFI data structure.
+        """
+        self._ptr = ptr
+        s = ffi.string(ptr)
+        self._string = s if isinstance(s, str) else s.decode("utf-8")
 
-    The copy must be deleted with `pactffi_string_delete`.
+    def __str__(self) -> str:
+        """
+        String representation of the Owned String.
+        """
+        return self._string
 
-    # Usage
+    def __repr__(self) -> str:
+        """
+        Debugging string representation of the Owned String.
+        """
+        return f"<OwnedString: {self._string!r}, ptr={self._ptr!r}>"
 
-    ```c
-    // Assuming `file_name` and `json_str` are already defined.
+    def __del__(self) -> None:
+        """
+        Destructor for the Owned String.
+        """
+        string_delete(self)
 
-    MessagePact *message_pact = pactffi_message_pact_new_from_json(file_name, json_str);
-    if (message_pact == NULLPTR) {
-        // handle error.
-    }
+    def __eq__(self, other: object) -> bool:
+        """
+        Equality comparison.
 
-    Consumer *consumer = pactffi_message_pact_get_consumer(message_pact);
-    if (consumer == NULLPTR) {
-        // handle error.
-    }
+        Args:
+            other:
+                The object to compare to.
 
-    char *name = pactffi_consumer_get_name(consumer);
-    if (name == NULL) {
-        // handle error.
-    }
+        Returns:
+            Whether the two objects are equal.
+        """
+        if isinstance(other, OwnedString):
+            return self._ptr == other._ptr
+        if isinstance(other, str):
+            return self._string == other
+        return super().__eq__(other)
 
-    printf("%s\n", name);
 
-    pactffi_string_delete(name);
-    ```
+def version() -> str:
+    """
+    Return the version of the pact_ffi library.
 
-    # Errors
+    [Rust `pactffi_version`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_version)
 
-    This function will fail if it is passed a NULL pointer,
-    or the Rust string contains an embedded NULL byte.
-    In the case of error, a NULL pointer will be returned.
+    Returns:
+        The version of the pact_ffi library as a string, in the form of `x.y.z`.
     """
-    raise NotImplementedError
+    v = ffi.string(lib.pactffi_version())
+    if isinstance(v, bytes):
+        return v.decode("utf-8")
+    return v
 
 
-def pact_get_consumer(pact: Pact) -> Consumer:
+def init(log_env_var: str) -> None:
     """
-    Get the consumer from a Pact.
+    Initialise the mock server library.
 
-    This returns a copy of the consumer model, and needs to be cleaned up with
-    `pactffi_pact_consumer_delete` when no longer required.
+    This can provide an environment variable name to use to set the log levels.
+    This function should only be called once, as it tries to install a global
+    tracing subscriber.
 
     [Rust
-    `pactffi_pact_get_consumer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_get_consumer)
+    `pactffi_init`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_init)
 
-    # Errors
+    # Safety
 
-    This function will fail if it is passed a NULL pointer. In the case of
-    error, a NULL pointer will be returned.
+    log_env_var must be a valid NULL terminated UTF-8 string.
     """
     raise NotImplementedError
 
 
-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)
+def init_with_log_level(level: str = "INFO") -> None:
     """
-    raise NotImplementedError
+    Initialises logging, and sets the log level explicitly.
 
+    This function should only be called once, as it tries to install a global
+    tracing subscriber.
 
-def message_contents_get_contents_str(contents: MessageContents) -> str:
-    """
-    Get the message contents in string form.
+    [Rust
+    `pactffi_init_with_log_level`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_init_with_log_level)
 
-    [Rust `pactffi_message_contents_get_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_get_contents_str)
+    Args:
+        level:
+            One of TRACE, DEBUG, INFO, WARN, ERROR, NONE/OFF. Case-insensitive.
 
     # Safety
 
-    The returned string must be deleted with `pactffi_string_delete`.
-
-    The returned string can outlive the message.
-
-    # Error Handling
-
-    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.
+    Exported functions are inherently unsafe.
     """
     raise NotImplementedError
 
 
-def message_contents_set_contents_str(
-    contents: MessageContents,
-    contents_str: str,
-    content_type: str,
-) -> None:
+def enable_ansi_support() -> None:
     """
-    Sets the contents of the message as a string.
+    Enable ANSI coloured output on Windows.
 
-    [Rust
-    `pactffi_message_contents_set_contents_str`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_set_contents_str)
+    On non-Windows platforms, this function is a no-op.
 
-    * `contents` - the message contents to set the contents for
-    * `contents_str` - pointer to contents to copy from. Must be a valid
-      NULL-terminated UTF-8 string pointer.
-    * `content_type` - pointer to the NULL-terminated UTF-8 string containing
-      the content type of the data.
+    [Rust
+    `pactffi_enable_ansi_support`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_enable_ansi_support)
 
     # 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 string 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.
+    This function is safe.
     """
     raise NotImplementedError
 
 
-def message_contents_get_contents_length(contents: MessageContents) -> int:
+def log_message(
+    message: str,
+    log_level: LevelFilter | str = LevelFilter.ERROR,
+    source: str | None = None,
+) -> None:
     """
-    Get the length of the message contents.
+    Log using the shared core logging facility.
 
-    [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_log_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_message)
 
-    # Safety
+    This is useful for callers to have a single set of logs.
 
-    This function is safe.
+    Args:
+        message:
+            The contents written to the log
 
-    # Error Handling
+        log_level:
+            The verbosity at which this message should be logged.
 
-    If the message is NULL, returns 0. If the body of the message
-    is missing, then this function also returns 0.
+        source:
+            The source of the log, such as the class, module or caller.
     """
-    raise NotImplementedError
+    if isinstance(log_level, str):
+        log_level = LevelFilter[log_level.upper()]
+    if source is None:
+        import inspect
+
+        source = inspect.stack()[1].function
+    lib.pactffi_log_message(
+        source.encode("utf-8"),
+        log_level.name.encode("utf-8"),
+        message.encode("utf-8"),
+    )
 
 
-def message_contents_get_contents_bin(contents: MessageContents) -> str:
+def mismatches_get_iter(mismatches: Mismatches) -> MismatchesIterator:
     """
-    Get the contents of a message as a pointer to an array of bytes.
+    Get an iterator over mismatches.
 
     [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_mismatches_get_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatches_get_iter)
+    """
+    raise NotImplementedError
 
-    # 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.
+def mismatches_delete(mismatches: Mismatches) -> None:
+    """
+    Delete mismatches.
 
-    # Error Handling
+    [Rust `pactffi_mismatches_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatches_delete)
+    """
+    raise NotImplementedError
 
-    If the message is NULL, returns NULL. If the body of the message is missing,
-    then this function also returns NULL.
+
+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.21/pact_ffi/?search=pactffi_mismatches_iter_next)
+
+    Returns a null pointer if no mismatches remain.
     """
     raise NotImplementedError
 
 
-def message_contents_set_contents_bin(
-    contents: MessageContents,
-    contents_bin: str,
-    len: int,
-    content_type: str,
-) -> None:
+def mismatches_iter_delete(iter: MismatchesIterator) -> None:
     """
-    Sets the contents of the message as an array of bytes.
+    Delete a mismatches iterator when you're done with it.
 
-    [Rust
-    `pactffi_message_contents_set_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_contents_set_contents_bin)
+    [Rust `pactffi_mismatches_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatches_iter_delete)
+    """
+    raise NotImplementedError
 
-    * `message` - the message contents to set the contents for
-    * `contents_bin` - pointer to contents to copy from
-    * `len` - number of bytes to copy from the contents pointer
-    * `content_type` - pointer to the NULL-terminated UTF-8 string containing
-      the content type of the data.
 
-    # Safety
+def mismatch_to_json(mismatch: Mismatch) -> str:
+    """
+    Get a JSON representation of the mismatch.
 
-    The contents pointer must be valid for reads of `len` bytes, and it must be
-    properly aligned and consecutive. Otherwise behaviour is undefined.
+    [Rust `pactffi_mismatch_to_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_to_json)
+    """
+    raise NotImplementedError
 
-    # 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.
+def mismatch_type(mismatch: Mismatch) -> str:
     """
-    raise NotImplementedError
+    Get the type of a mismatch.
 
+    [Rust `pactffi_mismatch_type`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_type)
+    """
+    raise NotImplementedError
 
-def message_contents_get_metadata_iter(
-    contents: MessageContents,
-) -> MessageMetadataIterator:
-    r"""
-    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)
+def mismatch_summary(mismatch: Mismatch) -> str:
+    """
+    Get a summary of a mismatch.
 
-    The returned pointer must be deleted with
-    `pactffi_message_metadata_iter_delete` when done with it.
+    [Rust `pactffi_mismatch_summary`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_summary)
+    """
+    raise NotImplementedError
 
-    # Safety
 
-    This iterator carries a pointer to the message contents, and must not
-    outlive the message.
+def mismatch_description(mismatch: Mismatch) -> str:
+    """
+    Get a description of a mismatch.
 
-    The message metadata also must not be modified during iteration. If it is,
-    the old iterator must be deleted and a new iterator created.
+    [Rust `pactffi_mismatch_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_description)
+    """
+    raise NotImplementedError
 
-    # Error Handling
 
-    On failure, this function will return a NULL pointer.
+def mismatch_ansi_description(mismatch: Mismatch) -> str:
+    """
+    Get an ANSI-compatible description of a mismatch.
 
-    This function may fail if any of the Rust strings contain embedded null
-    ('\0') bytes.
+    [Rust `pactffi_mismatch_ansi_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_mismatch_ansi_description)
     """
     raise NotImplementedError
 
 
-def message_contents_get_matching_rule_iter(
-    contents: MessageContents,
-    category: MatchingRuleCategory,
-) -> MatchingRuleCategoryIterator:
-    r"""
-    Get an iterator over the matching rules for a category of a message.
+def get_error_message(length: int = 1024) -> str | None:
+    """
+    Provide the error message from `LAST_ERROR` to the calling C code.
 
     [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)
-
-    The returned pointer must be deleted with
-    `pactffi_matching_rules_iter_delete` when done with it.
+    `pactffi_get_error_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_get_error_message)
 
-    Note that there could be multiple matching rules for the same key, so this
-    iterator will sequentially return each rule with the same key.
+    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
+    get more context on why the error happened.
 
-    For sample, given the following rules:
+    Do note that this error-reporting mechanism only reports the top-level error
+    message, not any source information embedded in the original Rust error
+    type. If you want more detailed information for debugging purposes, use the
+    logging interface.
 
-    ```
-    "$.a" => Type,
-    "$.b" => Regex("\\d+"), Number
-    ```
+    Args:
+        length:
+            The length of the buffer to allocate for the error message. If the
+            error message is longer than this, it will be truncated.
 
-    This iterator will return a sequence of 3 values:
+    Returns:
+        A string containing the error message, or None if there is no error
+        message.
 
-    - `("$.a", Type)`
-    - `("$.b", Regex("\d+"))`
-    - `("$.b", Number)`
+    Raises:
+        RuntimeError:
+            If the error message could not be retrieved.
+    """
+    buffer = ffi.new("char[]", length)
+    ret: int = lib.pactffi_get_error_message(buffer, length)
 
-    # Safety
+    if ret >= 0:
+        # While the documentation says that the return value is the number of bytes
+        # written, the actually return value is always 0 on success.
+        if msg := ffi.string(buffer):
+            if isinstance(msg, bytes):
+                return msg.decode("utf-8")
+            return msg
+        return None
+    if ret == -1:
+        msg = "The provided buffer is a null pointer."
+    elif ret == -2:  # noqa: PLR2004
+        # Instead of returning an error here, we call the function again with a
+        # larger buffer.
+        return get_error_message(length * 32)
+    elif ret == -3:  # noqa: PLR2004
+        msg = "The write failed for some other reason."
+    elif ret == -4:  # noqa: PLR2004
+        msg = "The error message had an interior NULL."
+    else:
+        msg = "An unknown error occurred."
+    raise RuntimeError(msg)
 
-    The iterator contains a copy of the data, so is safe to use when the message
-    or message contents has been deleted.
 
-    # Error Handling
+def log_to_stdout(level_filter: LevelFilter) -> int:
+    """
+    Convenience function to direct all logging to stdout.
 
-    On failure, this function will return a NULL pointer.
+    [Rust `pactffi_log_to_stdout`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_stdout)
     """
     raise NotImplementedError
 
 
-def request_contents_get_matching_rule_iter(
-    request: HttpRequest,
-    category: MatchingRuleCategory,
-) -> MatchingRuleCategoryIterator:
-    r"""
-    Get an iterator over the matching rules for a category of an HTTP request.
+def log_to_stderr(level_filter: LevelFilter | str = LevelFilter.ERROR) -> None:
+    """
+    Convenience function to direct all logging to stderr.
 
-    [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_log_to_stderr`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_stderr)
 
-    The returned pointer must be deleted with
-    `pactffi_matching_rules_iter_delete` when done with it.
+    Args:
+        level_filter:
+            The level of logs to filter to. If a string is given, it must match
+            one of the [`LevelFilter`][pact.v3.ffi.LevelFilter] values (case
+            insensitive).
 
-    For sample, given the following rules:
+    Raises:
+        RuntimeError:
+            If there was an error setting the logger.
+    """
+    if isinstance(level_filter, str):
+        level_filter = LevelFilter[level_filter.upper()]
+    ret: int = lib.pactffi_log_to_stderr(level_filter.value)
+    if ret != 0:
+        msg = "There was an unknown error setting the logger."
+        raise RuntimeError(msg)
 
-    ```
-    "$.a" => Type,
-    "$.b" => Regex("\d+"), Number
-    ```
 
-    This iterator will return a sequence of 3 values:
+def log_to_file(file_name: str, level_filter: LevelFilter) -> int:
+    """
+    Convenience function to direct all logging to a file.
 
-    - `("$.a", Type)`
-    - `("$.b", Regex("\d+"))`
-    - `("$.b", Number)`
+    [Rust
+    `pactffi_log_to_file`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_file)
 
     # Safety
 
-    The iterator contains a copy of the data, so is safe to use when the
-    interaction or request contents has been deleted.
-
-    # Error Handling
-
-    On failure, this function will return a NULL pointer.
+    This function will fail if the file_name pointer is invalid or does not
+    point to a NULL terminated string.
     """
     raise NotImplementedError
 
 
-def response_contents_get_matching_rule_iter(
-    response: HttpResponse,
-    category: MatchingRuleCategory,
-) -> MatchingRuleCategoryIterator:
-    r"""
-    Get an iterator over the matching rules for a category of an HTTP response.
+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_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_log_to_buffer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_log_to_buffer)
 
-    The returned pointer must be deleted with
-    `pactffi_matching_rules_iter_delete` when done with it.
+    Raises:
+        RuntimeError:
+            If there was an error setting the logger.
+    """
+    if isinstance(level_filter, str):
+        level_filter = LevelFilter[level_filter.upper()]
+    ret: int = lib.pactffi_log_to_buffer(level_filter.value)
+    if ret != 0:
+        msg = "There was an unknown error setting the logger."
+        raise RuntimeError(msg)
 
-    For sample, given the following rules:
 
-    ```
-    "$.a" => Type,
-    "$.b" => Regex("\d+"), Number
-    ```
+def logger_init() -> None:
+    """
+    Initialize the FFI logger with no sinks.
 
-    This iterator will return a sequence of 3 values:
+    [Rust `pactffi_logger_init`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_logger_init)
 
-    - `("$.a", Type)`
-    - `("$.b", Regex("\d+"))`
-    - `("$.b", Number)`
+    This initialized logger does nothing until `pactffi_logger_apply` has been called.
 
-    # Safety
+    # Usage
 
-    The iterator contains a copy of the data, so is safe to use when the
-    interaction or response contents has been deleted.
+    ```c
+    pactffi_logger_init();
+    ```
 
-    # Error Handling
+    # Safety
 
-    On failure, this function will return a NULL pointer.
+    This function is always safe to call.
     """
     raise NotImplementedError
 
 
-def message_contents_get_generators_iter(
-    contents: MessageContents,
-    category: GeneratorCategory,
-) -> GeneratorCategoryIterator:
+def logger_attach_sink(sink_specifier: str, level_filter: LevelFilter) -> int:
     """
-    Get an iterator over the generators for a category of a message.
+    Attach an additional sink to the thread-local logger.
 
     [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
+    `pactffi_logger_attach_sink`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_logger_attach_sink)
 
-    The iterator contains a copy of the data, so is safe to use when the message
-    or message contents has been deleted.
-
-    # Error Handling
+    This logger does nothing until `pactffi_logger_apply` has been called.
 
-    On failure, this function will return a NULL pointer.
-    """
-    raise NotImplementedError
+    Types of sinks can be specified:
 
+    - stdout (`pactffi_logger_attach_sink("stdout", LevelFilter_Info)`)
+    - stderr (`pactffi_logger_attach_sink("stderr", LevelFilter_Debug)`)
+    - file w/ file path (`pactffi_logger_attach_sink("file /some/file/path",
+      LevelFilter_Trace)`)
+    - buffer (`pactffi_logger_attach_sink("buffer", LevelFilter_Debug)`)
 
-def request_contents_get_generators_iter(
-    request: HttpRequest,
-    category: GeneratorCategory,
-) -> GeneratorCategoryIterator:
-    """
-    Get an iterator over the generators for a category of an HTTP request.
+    # Usage
 
-    [Rust
-    `pactffi_request_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_request_contents_get_generators_iter)
+    ```c
+    int result = pactffi_logger_attach_sink("file /some/file/path", LogLevel_Filter);
+    ```
 
-    The returned pointer must be deleted with `pactffi_generators_iter_delete`
-    when done with it.
+    # Error Handling
 
-    # Safety
+    The return error codes are as follows:
 
-    The iterator contains a copy of the data, so is safe to use when the
-    interaction or request contents has been deleted.
+    - `-1`: Can't set logger (applying the logger failed, perhaps because one is
+      applied already).
+    - `-2`: No logger has been initialized (call `pactffi_logger_init` before
+      any other log function).
+    - `-3`: The sink specifier was not UTF-8 encoded.
+    - `-4`: The sink type specified is not a known type (known types: "stdout",
+      "stderr", or "file /some/path").
+    - `-5`: No file path was specified in a file-type sink specification.
+    - `-6`: Opening a sink to the specified file path failed (check
+      permissions).
 
-    # Error Handling
+    # Safety
 
-    On failure, this function will return a NULL pointer.
+    This function checks the validity of the passed-in sink specifier, and
+    errors out if the specifier isn't valid UTF-8. Passing in an invalid or NULL
+    pointer will result in undefined behaviour.
     """
     raise NotImplementedError
 
 
-def response_contents_get_generators_iter(
-    response: HttpResponse,
-    category: GeneratorCategory,
-) -> GeneratorCategoryIterator:
+def logger_apply() -> int:
     """
-    Get an iterator over the generators for a category of an HTTP response.
+    Apply the previously configured sinks and levels to the program.
 
     [Rust
-    `pactffi_response_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_response_contents_get_generators_iter)
-
-    The returned pointer must be deleted with `pactffi_generators_iter_delete`
-    when done with it.
-
-    # Safety
+    `pactffi_logger_apply`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_logger_apply)
 
-    The iterator contains a copy of the data, so is safe to use when the
-    interaction or response contents has been deleted.
-
-    # Error Handling
+    If no sinks have been setup, will set the log level to info and the target
+    to standard out.
 
-    On failure, this function will return a NULL pointer.
+    This function will install a global tracing subscriber. Any attempts to
+    modify the logger after the call to `logger_apply` will fail.
     """
     raise NotImplementedError
 
 
-def parse_matcher_definition(expression: str) -> MatchingRuleDefinitionResult:
+def fetch_log_buffer(log_id: str) -> str:
     """
-    Parse a matcher definition string into a MatchingRuleDefinition.
-
-    The MatchingRuleDefition contains the example value, and matching rules and
-    any generator.
+    Fetch the in-memory logger buffer contents.
 
     [Rust
-    `pactffi_parse_matcher_definition`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_parse_matcher_definition)
+    `pactffi_fetch_log_buffer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_fetch_log_buffer)
 
-    The following are examples of matching rule definitions:
-
-    * `matching(type,'Name')` - type matcher with string value 'Name'
-    * `matching(number,100)` - number matcher
-    * `matching(datetime, 'yyyy-MM-dd','2000-01-01')` - datetime matcher with
-      format string
-
-    See [Matching Rule definition
-    expressions](https://docs.rs/pact_models/latest/pact_models/matchingrules/expressions/index.html).
+    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
+    with `pactffi_string_delete`.
 
-    The returned value needs to be freed up with the
-    `pactffi_matcher_definition_delete` function.
+    Fetches the logs associated with the provided identifier, or uses the
+    "global" one if the identifier is not specified (i.e. NULL).
 
-    # Errors If the expression is invalid, the MatchingRuleDefinition error will
-    be set. You can check for this value with the
-    `pactffi_matcher_definition_error` function.
+    Returns a NULL pointer if the buffer can't be fetched. This can occur is
+    there is not sufficient memory to make a copy of the contents or the buffer
+    contains non-UTF-8 characters.
 
     # Safety
 
-    This function is safe if the expression is a valid NULL terminated string
-    pointer.
+    This function will fail if the log_id pointer is invalid or does not point
+    to a NULL terminated string.
     """
     raise NotImplementedError
 
 
-def matcher_definition_error(definition: MatchingRuleDefinitionResult) -> str:
+def parse_pact_json(json: str) -> Pact:
     """
-    Returns any error message from parsing a matching definition expression.
-
-    If there is no error, it will return a NULL pointer, otherwise returns the
-    error message as a NULL-terminated string. The returned string must be freed
-    using the `pactffi_string_delete` function once done with it.
+    Parses the provided JSON into a Pact model.
 
     [Rust
-    `pactffi_matcher_definition_error`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_error)
-    """
-    raise NotImplementedError
-
-
-def matcher_definition_value(definition: MatchingRuleDefinitionResult) -> str:
-    """
-    Returns the value from parsing a matching definition expression.
+    `pactffi_parse_pact_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_parse_pact_json)
 
-    If there was an error, it will return a NULL pointer, otherwise returns the
-    value as a NULL-terminated string. The returned string must be freed using
-    the `pactffi_string_delete` function once done with it.
+    The returned Pact model must be freed with the `pactffi_pact_model_delete`
+    function when no longer needed.
 
-    [Rust
-    `pactffi_matcher_definition_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_value)
+    # Error Handling
 
-    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
-    value. This function will always return the string representation of the
-    value.
+    This function will return a NULL pointer if passed a NULL pointer or if an
+    error occurs.
     """
     raise NotImplementedError
 
 
-def matcher_definition_delete(definition: MatchingRuleDefinitionResult) -> None:
+def pact_model_delete(pact: Pact) -> None:
     """
-    Frees the memory used by the result of parsing the matching definition expression.
+    Frees the memory used by the Pact model.
 
-    [Rust `pactffi_matcher_definition_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_delete)
+    [Rust `pactffi_pact_model_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_model_delete)
     """
     raise NotImplementedError
 
 
-def matcher_definition_generator(definition: MatchingRuleDefinitionResult) -> Generator:
+def pact_model_interaction_iterator(pact: Pact) -> PactInteractionIterator:
     """
-    Returns the generator from parsing a matching definition expression.
-
-    If there was an error or there is no associated generator, it will return a
-    NULL pointer, otherwise returns the generator as a pointer.
+    Returns an iterator over all the interactions in the Pact.
 
     [Rust
-    `pactffi_matcher_definition_generator`](https://docs.rs/pact_ffi/0.4.19/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.
-    Using the generator pointer after the definition has been deleted will
-    result in undefined behaviour.
-    """
-    raise NotImplementedError
+    `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
+    copy of the interactions, so it will not be affected but mutations to the
+    Pact model and will still function if the Pact model is deleted.
 
-def matcher_definition_value_type(
-    definition: MatchingRuleDefinitionResult,
-) -> ExpressionValueType:
-    """
-    Returns the type of the value from parsing a matching definition expression.
-
-    If there was an error parsing the expression, it will return Unknown.
+    # Safety This function is safe as long as the Pact pointer is a valid
+    pointer.
 
-    [Rust
-    `pactffi_matcher_definition_value_type`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matcher_definition_value_type)
+    # Errors On any error, this function will return a NULL pointer.
     """
     raise NotImplementedError
 
 
-def matching_rule_iter_delete(iter: MatchingRuleIterator) -> None:
+def pact_spec_version(pact: Pact) -> PactSpecification:
     """
-    Free the iterator when you're done using it.
+    Returns the Pact specification enum that the Pact is for.
 
-    [Rust `pactffi_matching_rule_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_iter_delete)
+    [Rust `pactffi_pact_spec_version`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_spec_version)
     """
     raise NotImplementedError
 
 
-def matcher_definition_iter(
-    definition: MatchingRuleDefinitionResult,
-) -> MatchingRuleIterator:
+def pact_interaction_delete(interaction: PactInteraction) -> None:
     """
-    Returns an iterator over the matching rules from the parsed definition.
-
-    The iterator needs to be deleted with the
-    `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)
+    Frees the memory used by the Pact interaction model.
 
-    If there was an error parsing the expression, this function will return a
-    NULL pointer.
+    [Rust `pactffi_pact_interaction_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_delete)
     """
     raise NotImplementedError
 
 
-def matching_rule_iter_next(iter: MatchingRuleIterator) -> MatchingRuleResult:
+def async_message_new() -> AsynchronousMessage:
     """
-    Get the next matching rule or reference from the iterator.
-
-    As the values returned are owned by the iterator, they do not need to be
-    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)
+    Get a mutable pointer to a newly-created default message on the heap.
 
-    Will return a NULL pointer when the iterator has advanced past the end of
-    the list.
+    [Rust `pactffi_async_message_new`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_new)
 
     # Safety
 
@@ -2231,439 +2387,438 @@ def matching_rule_iter_next(iter: MatchingRuleIterator) -> MatchingRuleResult:
 
     # Error Handling
 
-    This function will return a NULL pointer if passed a NULL pointer or if an
-    error occurs.
+    Returns NULL on error.
     """
     raise NotImplementedError
 
 
-def matching_rule_id(rule_result: MatchingRuleResult) -> int:
+def async_message_delete(message: AsynchronousMessage) -> None:
     """
-    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)
-
-    The ID corresponds to the following rules:
-
-    | Rule | ID |
-    | ---- | -- |
-    | Equality | 1 |
-    | Regex | 2 |
-    | Type | 3 |
-    | MinType | 4 |
-    | MaxType | 5 |
-    | MinMaxType | 6 |
-    | Timestamp | 7 |
-    | Time | 8 |
-    | Date | 9 |
-    | Include | 10 |
-    | Number | 11 |
-    | Integer | 12 |
-    | Decimal | 13 |
-    | Null | 14 |
-    | ContentType | 15 |
-    | ArrayContains | 16 |
-    | Values | 17 |
-    | Boolean | 18 |
-    | StatusCode | 19 |
-    | NotEmpty | 20 |
-    | Semver | 21 |
-    | EachKey | 22 |
-    | EachValue | 23 |
-
-    # Safety
+    Destroy the `AsynchronousMessage` being pointed to.
 
-    This function is safe as long as the MatchingRuleResult pointer is a valid
-    pointer and the iterator has not been deleted.
+    [Rust `pactffi_async_message_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_async_message_delete(message._ptr)
 
 
-def matching_rule_value(rule_result: MatchingRuleResult) -> str:
+def async_message_get_contents(message: AsynchronousMessage) -> MessageContents | None:
     """
-    Returns the associated value for the matching rule.
-
-    If the matching rule does not have an associated value, will return a NULL
-    pointer.
+    Get the message contents of an `AsynchronousMessage` as a `MessageContents` pointer.
 
     [Rust
-    `pactffi_matching_rule_value`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_value)
-
-    The associated values for the rules are:
-
-    | Rule | ID | VALUE |
-    | ---- | -- | ----- |
-    | Equality | 1 | NULL |
-    | Regex | 2 | Regex value |
-    | Type | 3 | NULL |
-    | MinType | 4 | Minimum value |
-    | MaxType | 5 | Maximum value |
-    | MinMaxType | 6 | "min:max" |
-    | Timestamp | 7 | Format string |
-    | Time | 8 | Format string |
-    | Date | 9 | Format string |
-    | Include | 10 | String value |
-    | Number | 11 | NULL |
-    | Integer | 12 | NULL |
-    | Decimal | 13 | NULL |
-    | Null | 14 | NULL |
-    | ContentType | 15 | Content type |
-    | ArrayContains | 16 | NULL |
-    | Values | 17 | NULL |
-    | Boolean | 18 | NULL |
-    | StatusCode | 19 | NULL |
-    | NotEmpty | 20 | NULL |
-    | Semver | 21 | NULL |
-    | EachKey | 22 | NULL |
-    | EachValue | 23 | NULL |
-
-    Will return a NULL pointer if the matching rule was a reference or does not
-    have an associated value.
-
-    # Safety
+    `pactffi_async_message_get_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents)
 
-    This function is safe as long as the MatchingRuleResult pointer is a valid
-    pointer and the iterator it came from has not been deleted.
+    If the message contents are missing, this function will return `None`.
     """
-    raise NotImplementedError
+    return MessageContents(lib.pactffi_async_message_get_contents(message._ptr))
 
 
-def matching_rule_pointer(rule_result: MatchingRuleResult) -> MatchingRule:
+def async_message_generate_contents(
+    message: AsynchronousMessage,
+) -> MessageContents | None:
     """
-    Returns the matching rule pointer for the matching rule.
+    Get the message contents of an `AsynchronousMessage` as a `MessageContents` pointer.
 
-    Will return a NULL pointer if the matching rule result was a reference.
+    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_matching_rule_pointer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_pointer)
+    `pactffi_async_message_generate_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_generate_contents)
 
-    # Safety
-
-    This function is safe as long as the MatchingRuleResult pointer is a valid
-    pointer and the iterator it came from has not been deleted.
+    If the message contents are missing, this function will return `None`.
     """
-    raise NotImplementedError
+    return MessageContents(
+        lib.pactffi_async_message_generate_contents(message._ptr),
+        owned=False,
+    )
 
 
-def matching_rule_reference_name(rule_result: MatchingRuleResult) -> str:
+def async_message_get_contents_str(message: AsynchronousMessage) -> str:
     """
-    Return any matching rule reference to a attribute by name.
+    Get the message contents of an `AsynchronousMessage` in string form.
 
-    This is when the matcher should be configured to match the type of a
-    structure. I.e.,
+    [Rust `pactffi_async_message_get_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents_str)
 
-    [Rust
-    `pactffi_matching_rule_reference_name`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rule_reference_name)
+    # Safety
 
-    ```json
-    {
-      "pact:match": "eachValue(matching($'person'))",
-      "person": {
-        "name": "Fred",
-        "age": 100
-      }
-    }
-    ```
+    The returned string must be deleted with `pactffi_string_delete`.
 
-    Will return a NULL pointer if the matching rule was not a reference.
+    The returned string can outlive the message.
 
-    # Safety
+    # Error Handling
 
-    This function is safe as long as the MatchingRuleResult pointer is a valid
-    pointer and the iterator has not been deleted.
+    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 validate_datetime(value: str, format: str) -> None:
+def async_message_set_contents_str(
+    message: AsynchronousMessage,
+    contents: str,
+    content_type: 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`.
+    Sets the contents of the message as a string.
 
     [Rust
-    `pactffi_validate_datetime`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_validate_datetime)
+    `pactffi_async_message_set_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_set_contents_str)
 
-    # 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`.
+    - `message` - the message to set the contents for
+    - `contents` - pointer to contents to copy from. Must be a valid
+      NULL-terminated UTF-8 string pointer.
+    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
+      the content type of the data.
 
     # Safety
 
-    This function is safe as long as the value and format parameters point to
-    valid NULL-terminated strings.
+    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.
     """
-    ret = lib.pactffi_validate_datetime(value.encode(), format.encode())
-    if ret == 0:
-        return
-    if ret == 1:
-        msg = f"Invalid datetime value {value!r}' for format {format!r}"
-        raise ValueError(msg)
-    if ret == 2:  # noqa: PLR2004
-        msg = f"Panic while validating datetime value: {get_error_message()}"
-    else:
-        msg = f"Unknown error while validating datetime value: {ret}"
-    raise RuntimeError(msg)
+    raise NotImplementedError
 
 
-def generator_to_json(generator: Generator) -> str:
+def async_message_get_contents_length(message: AsynchronousMessage) -> int:
     """
-    Get the JSON form of the generator.
+    Get the length of the contents of a `AsynchronousMessage`.
 
     [Rust
-    `pactffi_generator_to_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generator_to_json)
-
-    The returned string must be deleted with `pactffi_string_delete`.
+    `pactffi_async_message_get_contents_length`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents_length)
 
     # Safety
 
-    This function will fail if it is passed a NULL pointer, or the owner of the
-    generator has been deleted.
+    This function is safe.
+
+    # Error Handling
+
+    If the message is NULL, returns 0. If the body of the request is missing,
+    then this function also returns 0.
     """
     raise NotImplementedError
 
 
-def generator_generate_string(generator: Generator, context_json: str) -> str:
+def async_message_get_contents_bin(message: AsynchronousMessage) -> str:
     """
-    Generate a string value using the provided generator.
-
-    An optional JSON payload containing any generator context ca be given. The
-    context value is used for generators like `MockServerURL` (which should
-    contain details about the running mock server) and `ProviderStateGenerator`
-    (which should be the values returned from the Provider State callback
-    function).
+    Get the contents of an `AsynchronousMessage` as bytes.
 
     [Rust
-    `pactffi_generator_generate_string`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generator_generate_string)
+    `pactffi_async_message_get_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_contents_bin)
 
-    If anything goes wrong, it will return a NULL pointer.
+    # Safety
+
+    The number of bytes in the buffer will be returned by
+    `pactffi_async_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 generator_generate_integer(generator: Generator, context_json: str) -> int:
+def async_message_set_contents_bin(
+    message: AsynchronousMessage,
+    contents: str,
+    len: int,
+    content_type: str,
+) -> None:
     """
-    Generate an integer value using the provided generator.
-
-    An optional JSON payload containing any generator context can be given. The
-    context value is used for generators like `ProviderStateGenerator` (which
-    should be the values returned from the Provider State callback function).
+    Sets the contents of the message as an array of bytes.
 
     [Rust
-    `pactffi_generator_generate_integer`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generator_generate_integer)
+    `pactffi_async_message_set_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_set_contents_bin)
 
-    If anything goes wrong or the generator is not a type that can generate an
-    integer value, it will return a zero value.
+    * `message` - the message to set the contents for
+    * `contents` - pointer to contents to copy from
+    * `len` - number of bytes to copy from the contents pointer
+    * `content_type` - pointer to the NULL-terminated UTF-8 string containing
+      the content type of the data.
+
+    # 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 generators_iter_delete(iter: GeneratorCategoryIterator) -> None:
-    """
-    Free the iterator when you're done using it.
+def async_message_get_description(message: AsynchronousMessage) -> str:
+    r"""
+    Get a copy of the description.
 
     [Rust
-    `pactffi_generators_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generators_iter_delete)
+    `pactffi_async_message_get_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_get_description)
+
+    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 generators_iter_next(iter: GeneratorCategoryIterator) -> GeneratorKeyValuePair:
+def async_message_set_description(
+    message: AsynchronousMessage,
+    description: str,
+) -> int:
     """
-    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)
+    Write the `description` field on the `AsynchronousMessage`.
 
-    The returned pointer must be deleted with
-    `pactffi_generator_iter_pair_delete`.
+    [Rust `pactffi_async_message_set_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_async_message_set_description)
 
     # Safety
 
-    The underlying data is owned by the `GeneratorKeyValuePair`, so is always
-    safe to use.
+    `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
 
-    If no further data is present, returns NULL.
+    Errors will be reported with a non-zero return value.
     """
     raise NotImplementedError
 
 
-def generators_iter_pair_delete(pair: GeneratorKeyValuePair) -> None:
-    """
-    Free a pair of key and value returned from `pactffi_generators_iter_next`.
+def async_message_get_provider_state(
+    message: AsynchronousMessage,
+    index: int,
+) -> ProviderState:
+    r"""
+    Get a copy of the provider state at the given index from this message.
 
     [Rust
-    `pactffi_generators_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_generators_iter_pair_delete)
+    `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:
+            If the provider state cannot be retrieved.
     """
-    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 sync_http_new() -> SynchronousHttp:
+def async_message_get_provider_state_iter(
+    message: AsynchronousMessage,
+) -> ProviderStateIterator:
     """
-    Get a mutable pointer to a newly-created default interaction on the heap.
+    Get an iterator over provider states.
 
-    [Rust `pactffi_sync_http_new`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_new)
+    [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
 
-    This function is safe.
+    The underlying data must not change during iteration.
+    """
+    return ProviderStateIterator(
+        lib.pactffi_async_message_get_provider_state_iter(message._ptr)
+    )
 
-    # Error Handling
 
-    Returns NULL on error.
-    """
-    raise NotImplementedError
+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.21/pact_ffi/?search=pactffi_consumer_get_name)
 
-def sync_http_delete(interaction: SynchronousHttp) -> None:
-    """
-    Destroy the `SynchronousHttp` interaction being pointed to.
+    The copy must be deleted with `pactffi_string_delete`.
 
-    [Rust
-    `pactffi_sync_http_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_delete)
+    # Usage
+
+    ```c
+    // Assuming `file_name` and `json_str` are already defined.
+
+    MessagePact *message_pact = pactffi_message_pact_new_from_json(file_name, json_str);
+    if (message_pact == NULLPTR) {
+        // handle error.
+    }
+
+    Consumer *consumer = pactffi_message_pact_get_consumer(message_pact);
+    if (consumer == NULLPTR) {
+        // handle error.
+    }
+
+    char *name = pactffi_consumer_get_name(consumer);
+    if (name == NULL) {
+        // handle error.
+    }
+
+    printf("%s\n", name);
+
+    pactffi_string_delete(name);
+    ```
+
+    # Errors
+
+    This function will fail if it is passed a NULL pointer,
+    or the Rust string contains an embedded NULL byte.
+    In the case of error, a NULL pointer will be returned.
     """
     raise NotImplementedError
 
 
-def sync_http_get_request(interaction: SynchronousHttp) -> HttpRequest:
+def pact_get_consumer(pact: Pact) -> Consumer:
     """
-    Get the request of a `SynchronousHttp` interaction.
+    Get the consumer from a Pact.
+
+    This returns a copy of the consumer model, and needs to be cleaned up with
+    `pactffi_pact_consumer_delete` when no longer required.
 
     [Rust
-    `pactffi_sync_http_get_request`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request)
+    `pactffi_pact_get_consumer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_get_consumer)
 
-    # Safety
+    # Errors
 
-    The data pointed to by the pointer this function returns will be deleted
-    when the interaction is deleted. Trying to use if after the interaction is
-    deleted will result in undefined behaviour.
+    This function will fail if it is passed a NULL pointer. In the case of
+    error, a NULL pointer will be returned.
+    """
+    raise NotImplementedError
 
-    # Error Handling
 
-    If the interaction is NULL, returns NULL.
+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.21/pact_ffi/?search=pactffi_pact_consumer_delete)
     """
     raise NotImplementedError
 
 
-def sync_http_get_request_contents(interaction: SynchronousHttp) -> str:
+def message_contents_delete(contents: MessageContents) -> None:
     """
-    Get the request contents of a `SynchronousHttp` interaction in string form.
+    Delete the message contents instance.
 
-    [Rust
-    `pactffi_sync_http_get_request_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_request_contents)
+    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.21/pact_ffi/?search=pactffi_message_contents_delete)
+    """
+    lib.pactffi_message_contents_delete(contents._ptr)
 
-    The returned string can outlive the interaction.
 
-    # Error Handling
+def message_contents_get_contents_str(contents: MessageContents) -> str | None:
+    """
+    Get the message contents in string form.
 
-    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.
+    [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`.
     """
-    raise NotImplementedError
+    ptr = lib.pactffi_message_contents_get_contents_str(contents._ptr)
+    if ptr == ffi.NULL:
+        return None
+    return OwnedString(ptr)
 
 
-def sync_http_set_request_contents(
-    interaction: SynchronousHttp,
-    contents: str,
+def message_contents_set_contents_str(
+    contents: MessageContents,
+    contents_str: str,
     content_type: str,
 ) -> None:
     """
-    Sets the request contents of the interaction.
+    Sets the contents of the message as a string.
 
     [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_message_contents_set_contents_str`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_set_contents_str)
 
-    - `interaction` - the interaction to set the request contents for
-    - `contents` - pointer to contents to copy from. Must be a valid
+    * `contents` - the message contents to set the contents for
+    * `contents_str` - pointer to contents to copy from. Must be a valid
       NULL-terminated UTF-8 string pointer.
-    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
+    * `content_type` - pointer to the NULL-terminated UTF-8 string containing
       the content type of the data.
 
     # Safety
 
-    The request contents and content type must either be NULL pointers, or point
+    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 request contents as null.
-    If the content type is a null pointer, or can't be parsed, it will set the
-    content type as unknown.
+    If the contents string 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 sync_http_get_request_contents_length(interaction: SynchronousHttp) -> int:
+def message_contents_get_contents_length(contents: MessageContents) -> 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)
-
-    # Safety
-
-    This function is safe.
+    Get the length of the message contents.
 
-    # Error Handling
+    [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 interaction is NULL, returns 0. If the body of the request 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 sync_http_get_request_contents_bin(interaction: SynchronousHttp) -> bytes:
+def message_contents_get_contents_bin(contents: MessageContents) -> bytes | None:
     """
-    Get the request contents of a `SynchronousHttp` interaction as bytes.
+    Get the contents of a message as a pointer to an array of 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
+    `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 interaction is NULL, returns NULL. If the body of the request 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 sync_http_set_request_contents_bin(
-    interaction: SynchronousHttp,
-    contents: str,
+def message_contents_set_contents_bin(
+    contents: MessageContents,
+    contents_bin: str,
     len: int,
     content_type: str,
 ) -> None:
     """
-    Sets the request contents of the interaction as an array of bytes.
+    Sets the contents of the message 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_message_contents_set_contents_bin`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_set_contents_bin)
 
-    - `interaction` - the interaction to set the request contents for
-    - `contents` - pointer to contents to copy from
-    - `len` - number of bytes to copy from the contents pointer
-    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
+    * `message` - the message contents to set the contents for
+    * `contents_bin` - pointer to contents to copy from
+    * `len` - number of bytes to copy from the contents pointer
+    * `content_type` - pointer to the NULL-terminated UTF-8 string containing
       the content type of the data.
 
     # Safety
@@ -2673,671 +2828,937 @@ def sync_http_set_request_contents_bin(
 
     # Error Handling
 
-    If the contents is a NULL pointer, it will set the request contents as null.
+    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 sync_http_get_response(interaction: SynchronousHttp) -> HttpResponse:
-    """
-    Get the response of a `SynchronousHttp` interaction.
+def message_contents_get_metadata_iter(
+    contents: MessageContents,
+) -> MessageMetadataIterator:
+    r"""
+    Get an iterator over the metadata of a message.
 
     [Rust
-    `pactffi_sync_http_get_response`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_response)
+    `pactffi_message_contents_get_metadata_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_metadata_iter)
 
     # Safety
 
-    The data pointed to by the pointer this function returns will be deleted
-    when the interaction is deleted. Trying to use if after the interaction is
-    deleted will result in undefined behaviour.
+    This iterator carries a pointer to the message contents, and must not
+    outlive the message.
 
-    # Error Handling
+    The message metadata also must not be modified during iteration. If it is,
+    the old iterator must be deleted and a new iterator created.
 
-    If the interaction is NULL, returns NULL.
+    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 sync_http_get_response_contents(interaction: SynchronousHttp) -> str:
-    """
-    Get the response contents of a `SynchronousHttp` interaction in string form.
+def message_contents_get_matching_rule_iter(
+    contents: MessageContents,
+    category: MatchingRuleCategory,
+) -> MatchingRuleCategoryIterator:
+    r"""
+    Get an iterator over the matching rules for a category of a message.
 
     [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_message_contents_get_matching_rule_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_matching_rule_iter)
 
-    # Safety
+    The returned pointer must be deleted with
+    `pactffi_matching_rules_iter_delete` when done with it.
 
-    The returned string must be deleted with `pactffi_string_delete`.
+    Note that there could be multiple matching rules for the same key, so this
+    iterator will sequentially return each rule with the same key.
+
+    For sample, given the following rules:
+
+    ```
+    "$.a" => Type,
+    "$.b" => Regex("\\d+"), Number
+    ```
+
+    This iterator will return a sequence of 3 values:
+
+    - `("$.a", Type)`
+    - `("$.b", Regex("\d+"))`
+    - `("$.b", Number)`
+
+    # Safety
 
-    The returned string can outlive the interaction.
+    The iterator contains a copy of the data, so is safe to use when the message
+    or message contents has been deleted.
 
     # Error Handling
 
-    If the interaction is NULL, returns NULL.
+    On failure, this function will return a NULL pointer.
+    """
+    return MatchingRuleCategoryIterator(
+        lib.pactffi_message_contents_get_matching_rule_iter(contents._ptr, category)
+    )
+
+
+def request_contents_get_matching_rule_iter(
+    request: HttpRequest,
+    category: MatchingRuleCategory,
+) -> MatchingRuleCategoryIterator:
+    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.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.
+
+    For sample, given the following rules:
+
+    ```
+    "$.a" => Type,
+    "$.b" => Regex("\d+"), Number
+    ```
+
+    This iterator will return a sequence of 3 values:
+
+    - `("$.a", Type)`
+    - `("$.b", Regex("\d+"))`
+    - `("$.b", Number)`
+
+    # Safety
+
+    The iterator contains a copy of the data, so is safe to use when the
+    interaction or request contents has been deleted.
 
-    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.
+    # Error Handling
+
+    On failure, this function will return a NULL pointer.
     """
     raise NotImplementedError
 
 
-def sync_http_set_response_contents(
-    interaction: SynchronousHttp,
-    contents: str,
-    content_type: str,
-) -> None:
-    """
-    Sets the response contents of the interaction.
+def response_contents_get_matching_rule_iter(
+    response: HttpResponse,
+    category: MatchingRuleCategory,
+) -> MatchingRuleCategoryIterator:
+    r"""
+    Get an iterator over the matching rules for a category of an HTTP response.
 
-    [Rust
-    `pactffi_sync_http_set_response_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_set_response_contents)
+    [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)
 
-    - `interaction` - the interaction to set the response contents for
-    - `contents` - pointer to contents to copy from. Must be a valid
-      NULL-terminated UTF-8 string pointer.
-    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
-      the content type of the data.
+    The returned pointer must be deleted with
+    `pactffi_matching_rules_iter_delete` when done with it.
+
+    For sample, given the following rules:
+
+    ```
+    "$.a" => Type,
+    "$.b" => Regex("\d+"), Number
+    ```
+
+    This iterator will return a sequence of 3 values:
+
+    - `("$.a", Type)`
+    - `("$.b", Regex("\d+"))`
+    - `("$.b", Number)`
 
     # Safety
 
-    The response contents and content type must either be NULL pointers, or
-    point to valid UTF-8 encoded NULL-terminated strings. Otherwise behaviour is
-    undefined.
+    The iterator contains a copy of the data, so is safe to use when the
+    interaction or response contents has been deleted.
 
     # Error Handling
 
-    If the contents is a NULL pointer, it will set the response contents as
-    null. If the content type is a null pointer, or can't be parsed, it will set
-    the content type as unknown.
+    On failure, this function will return a NULL pointer.
     """
     raise NotImplementedError
 
 
-def sync_http_get_response_contents_length(interaction: SynchronousHttp) -> int:
+def message_contents_get_generators_iter(
+    contents: MessageContents,
+    category: GeneratorCategory,
+) -> GeneratorCategoryIterator:
     """
-    Get the length of the response contents of a `SynchronousHttp` interaction.
+    Get an iterator over the generators for a category of a message.
 
     [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_message_contents_get_generators_iter`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_contents_get_generators_iter)
 
     # Safety
 
-    This function is safe.
+    The iterator contains a copy of the data, so is safe to use when the message
+    or message contents has been deleted.
+
+    Raises:
+        RuntimeError:
+            If the generators iterator cannot be retrieved.
+    """
+    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(
+    request: HttpRequest,
+    category: GeneratorCategory,
+) -> GeneratorCategoryIterator:
+    """
+    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.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.
+
+    # Safety
+
+    The iterator contains a copy of the data, so is safe to use when the
+    interaction or request contents has been deleted.
 
     # 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.
+    On failure, this function will return a NULL pointer.
     """
     raise NotImplementedError
 
 
-def sync_http_get_response_contents_bin(interaction: SynchronousHttp) -> bytes:
+def response_contents_get_generators_iter(
+    response: HttpResponse,
+    category: GeneratorCategory,
+) -> GeneratorCategoryIterator:
     """
-    Get the response contents of a `SynchronousHttp` interaction as bytes.
+    Get an iterator over the generators for a category of an HTTP response.
 
     [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_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.
 
     # 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.
+    The iterator contains a copy of the data, so is safe to use when the
+    interaction or response contents has been deleted.
 
     # Error Handling
 
-    If the interaction is NULL, returns NULL. If the body of the response is
-    missing, then this function also returns NULL.
+    On failure, this function will return a NULL pointer.
     """
     raise NotImplementedError
 
 
-def sync_http_set_response_contents_bin(
-    interaction: SynchronousHttp,
-    contents: str,
-    len: int,
-    content_type: str,
-) -> None:
+def parse_matcher_definition(expression: str) -> MatchingRuleDefinitionResult:
     """
-    Sets the response contents of the `SynchronousHttp` interaction as bytes.
+    Parse a matcher definition string into a MatchingRuleDefinition.
+
+    The MatchingRuleDefition contains the example value, and matching rules and
+    any generator.
 
     [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_parse_matcher_definition`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_parse_matcher_definition)
 
-    - `interaction` - the interaction to set the response contents for
-    - `contents` - pointer to contents to copy from
-    - `len` - number of bytes to copy
-    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
-      the content type of the data.
+    The following are examples of matching rule definitions:
 
-    # Safety
+    * `matching(type,'Name')` - type matcher with string value 'Name'
+    * `matching(number,100)` - number matcher
+    * `matching(datetime, 'yyyy-MM-dd','2000-01-01')` - datetime matcher with
+      format string
 
-    The contents pointer must be valid for reads of `len` bytes, and it must be
-    properly aligned and consecutive. Otherwise behaviour is undefined.
+    See [Matching Rule definition
+    expressions](https://docs.rs/pact_models/latest/pact_models/matchingrules/expressions/index.html).
 
-    # Error Handling
+    The returned value needs to be freed up with the
+    `pactffi_matcher_definition_delete` function.
 
-    If the contents is a NULL pointer, it will set the response contents as
-    null. If the content type is a null pointer, or can't be parsed, it will set
-    the content type as unknown.
+    # Errors If the expression is invalid, the MatchingRuleDefinition error will
+    be set. You can check for this value with the
+    `pactffi_matcher_definition_error` function.
+
+    # Safety
+
+    This function is safe if the expression is a valid NULL terminated string
+    pointer.
     """
     raise NotImplementedError
 
 
-def sync_http_get_description(interaction: SynchronousHttp) -> str:
-    r"""
-    Get a copy of the description.
+def matcher_definition_error(definition: MatchingRuleDefinitionResult) -> str:
+    """
+    Returns any error message from parsing a matching definition expression.
 
-    [Rust
-    `pactffi_sync_http_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_get_description)
+    If there is no error, it will return a NULL pointer, otherwise returns the
+    error message as a NULL-terminated string. The returned string must be freed
+    using the `pactffi_string_delete` function once done with it.
 
-    # Safety
+    [Rust
+    `pactffi_matcher_definition_error`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_error)
+    """
+    raise NotImplementedError
 
-    The returned string must be deleted with `pactffi_string_delete`.
 
-    Since it is a copy, the returned string may safely outlive the
-    `SynchronousHttp` interaction.
+def matcher_definition_value(definition: MatchingRuleDefinitionResult) -> str:
+    """
+    Returns the value from parsing a matching definition expression.
 
-    # Errors
+    If there was an error, it will return a NULL pointer, otherwise returns the
+    value as a NULL-terminated string. The returned string must be freed using
+    the `pactffi_string_delete` function once done with it.
 
-    On failure, this function will return a NULL pointer.
+    [Rust
+    `pactffi_matcher_definition_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_value)
 
-    This function may fail if the Rust string contains embedded null ('\0')
-    bytes.
+    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
+    value. This function will always return the string representation of the
+    value.
     """
     raise NotImplementedError
 
 
-def sync_http_set_description(interaction: SynchronousHttp, description: str) -> int:
+def matcher_definition_delete(definition: MatchingRuleDefinitionResult) -> None:
     """
-    Write the `description` field on the `SynchronousHttp`.
+    Frees the memory used by the result of parsing the matching definition expression.
 
-    [Rust
-    `pactffi_sync_http_set_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_http_set_description)
+    [Rust `pactffi_matcher_definition_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_delete)
+    """
+    raise NotImplementedError
 
-    # Safety
 
-    `description` must contain valid UTF-8. Invalid UTF-8 will be replaced with
-    U+FFFD REPLACEMENT CHARACTER.
+def matcher_definition_generator(definition: MatchingRuleDefinitionResult) -> Generator:
+    """
+    Returns the generator from parsing a matching definition expression.
 
-    This function will only reallocate if the new string does not fit in the
-    existing buffer.
+    If there was an error or there is no associated generator, it will return a
+    NULL pointer, otherwise returns the generator as a pointer.
 
-    # Error Handling
+    [Rust
+    `pactffi_matcher_definition_generator`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_generator)
 
-    Errors will be reported with a non-zero return value.
+    The generator pointer will be a valid pointer as long as
+    `pactffi_matcher_definition_delete` has not been called on the definition.
+    Using the generator pointer after the definition has been deleted will
+    result in undefined behaviour.
     """
     raise NotImplementedError
 
 
-def sync_http_get_provider_state(
-    interaction: SynchronousHttp,
-    index: int,
-) -> ProviderState:
-    r"""
-    Get a copy of the provider state at the given index from this interaction.
+def matcher_definition_value_type(
+    definition: MatchingRuleDefinitionResult,
+) -> ExpressionValueType:
+    """
+    Returns the type of the value from parsing a matching definition expression.
+
+    If there was an error parsing the expression, it will return Unknown.
 
     [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_matcher_definition_value_type`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matcher_definition_value_type)
+    """
+    raise NotImplementedError
 
-    # Safety
 
-    The returned structure must be deleted with `provider_state_delete`.
+def matching_rule_iter_delete(iter: MatchingRuleIterator) -> None:
+    """
+    Free the iterator when you're done using it.
 
-    Since it is a copy, the returned structure may safely outlive the
-    `SynchronousHttp`.
+    [Rust `pactffi_matching_rule_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_iter_delete)
+    """
+    raise NotImplementedError
 
-    # Error Handling
 
-    On failure, this function will return a variant other than Success.
+def matcher_definition_iter(
+    definition: MatchingRuleDefinitionResult,
+) -> MatchingRuleIterator:
+    """
+    Returns an iterator over the matching rules from the parsed definition.
 
-    This function may fail if the index requested is out of bounds, or if any of
-    the Rust strings contain embedded null ('\0') bytes.
+    The iterator needs to be deleted with the
+    `pactffi_matching_rule_iter_delete` function once done with it.
+
+    [Rust
+    `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.
     """
     raise NotImplementedError
 
 
-def sync_http_get_provider_state_iter(
-    interaction: SynchronousHttp,
-) -> ProviderStateIterator:
+def matching_rule_iter_next(iter: MatchingRuleIterator) -> MatchingRuleResult:
     """
-    Get an iterator over provider states.
+    Get the next matching rule or reference from the iterator.
+
+    As the values returned are owned by the iterator, they do not need to be
+    deleted but will be cleaned up when the iterator is deleted.
 
     [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_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.
 
     # Safety
 
-    The underlying data must not change during iteration.
+    This function is safe.
 
     # Error Handling
 
-    Returns NULL if an error occurs.
+    This function will return a NULL pointer if passed a NULL pointer or if an
+    error occurs.
     """
     raise NotImplementedError
 
 
-def pact_interaction_as_synchronous_http(
-    interaction: PactInteraction,
-) -> SynchronousHttp:
-    r"""
-    Casts this interaction to a `SynchronousHttp` interaction.
-
-    Returns a NULL pointer if the interaction can not be casted to a
-    `SynchronousHttp` interaction (for instance, it is a message interaction).
-    The returned pointer must be freed with `pactffi_sync_http_delete` when no
-    longer required.
+def matching_rule_id(rule_result: MatchingRuleResult) -> int:
+    """
+    Return the ID of the matching rule.
 
     [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_matching_rule_id`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_id)
 
-    # Safety This function is safe as long as the interaction pointer is a valid
-    pointer.
+    The ID corresponds to the following rules:
 
-    # Errors On any error, this function will return a NULL pointer.
+    | Rule | ID |
+    | ---- | -- |
+    | Equality | 1 |
+    | Regex | 2 |
+    | Type | 3 |
+    | MinType | 4 |
+    | MaxType | 5 |
+    | MinMaxType | 6 |
+    | Timestamp | 7 |
+    | Time | 8 |
+    | Date | 9 |
+    | Include | 10 |
+    | Number | 11 |
+    | Integer | 12 |
+    | Decimal | 13 |
+    | Null | 14 |
+    | ContentType | 15 |
+    | ArrayContains | 16 |
+    | Values | 17 |
+    | Boolean | 18 |
+    | StatusCode | 19 |
+    | NotEmpty | 20 |
+    | Semver | 21 |
+    | EachKey | 22 |
+    | EachValue | 23 |
+
+    # Safety
+
+    This function is safe as long as the MatchingRuleResult pointer is a valid
+    pointer and the iterator has not been deleted.
     """
     raise NotImplementedError
 
 
-def pact_interaction_as_message(interaction: PactInteraction) -> Message:
+def matching_rule_value(rule_result: MatchingRuleResult) -> str:
     """
-    Casts this interaction to a `Message` interaction.
+    Returns the associated value for the matching rule.
 
-    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.
+    If the matching rule does not have an associated value, will return a NULL
+    pointer.
 
     [Rust
-    `pactffi_pact_interaction_as_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_as_message)
+    `pactffi_matching_rule_value`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_value)
 
-    Note that if the interaction is a V4 `AsynchronousMessage`, it will be
-    converted to a V3 `Message` before being returned.
+    The associated values for the rules are:
 
-    # Safety This function is safe as long as the interaction pointer is a valid
-    pointer.
+    | Rule | ID | VALUE |
+    | ---- | -- | ----- |
+    | Equality | 1 | NULL |
+    | Regex | 2 | Regex value |
+    | Type | 3 | NULL |
+    | MinType | 4 | Minimum value |
+    | MaxType | 5 | Maximum value |
+    | MinMaxType | 6 | "min:max" |
+    | Timestamp | 7 | Format string |
+    | Time | 8 | Format string |
+    | Date | 9 | Format string |
+    | Include | 10 | String value |
+    | Number | 11 | NULL |
+    | Integer | 12 | NULL |
+    | Decimal | 13 | NULL |
+    | Null | 14 | NULL |
+    | ContentType | 15 | Content type |
+    | ArrayContains | 16 | NULL |
+    | Values | 17 | NULL |
+    | Boolean | 18 | NULL |
+    | StatusCode | 19 | NULL |
+    | NotEmpty | 20 | NULL |
+    | Semver | 21 | NULL |
+    | EachKey | 22 | NULL |
+    | EachValue | 23 | NULL |
 
-    # Errors On any error, this function will return a NULL pointer.
+    Will return a NULL pointer if the matching rule was a reference or does not
+    have an associated value.
+
+    # Safety
+
+    This function is safe as long as the MatchingRuleResult pointer is a valid
+    pointer and the iterator it came from has not been deleted.
     """
     raise NotImplementedError
 
 
-def pact_interaction_as_asynchronous_message(
-    interaction: PactInteraction,
-) -> AsynchronousMessage:
+def matching_rule_pointer(rule_result: MatchingRuleResult) -> MatchingRule:
     """
-    Casts this interaction to a `AsynchronousMessage` interaction.
-
-    Returns a NULL pointer if the interaction can not be casted to a
-    `AsynchronousMessage` interaction (for instance, it is a http interaction).
-    The returned pointer must be freed with `pactffi_async_message_delete` when
-    no longer required.
+    Returns the matching rule pointer for the matching rule.
 
-    [Rust
-    `pactffi_pact_interaction_as_asynchronous_message`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_as_asynchronous_message)
+    Will return a NULL pointer if the matching rule result was a reference.
 
-    Note that if the interaction is a V3 `Message`, it will be converted to a V4
-    `AsynchronousMessage` before being returned.
+    [Rust
+    `pactffi_matching_rule_pointer`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_pointer)
 
-    # Safety This function is safe as long as the interaction pointer is a valid
-    pointer.
+    # Safety
 
-    # Errors On any error, this function will return a NULL pointer.
+    This function is safe as long as the MatchingRuleResult pointer is a valid
+    pointer and the iterator it came from has not been deleted.
     """
     raise NotImplementedError
 
 
-def pact_interaction_as_synchronous_message(
-    interaction: PactInteraction,
-) -> SynchronousMessage:
+def matching_rule_reference_name(rule_result: MatchingRuleResult) -> str:
     """
-    Casts this interaction to a `SynchronousMessage` interaction.
+    Return any matching rule reference to a attribute by name.
 
-    Returns a NULL pointer if the interaction can not be casted to a
-    `SynchronousMessage` interaction (for instance, it is a http interaction).
-    The returned pointer must be freed with `pactffi_sync_message_delete` when
-    no longer required.
+    This is when the matcher should be configured to match the type of a
+    structure. I.e.,
 
     [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_matching_rule_reference_name`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_reference_name)
 
-    # Safety This function is safe as long as the interaction pointer is a valid
-    pointer.
+    ```json
+    {
+      "pact:match": "eachValue(matching($'person'))",
+      "person": {
+        "name": "Fred",
+        "age": 100
+      }
+    }
+    ```
 
-    # Errors On any error, this function will return a NULL pointer.
+    Will return a NULL pointer if the matching rule was not a reference.
+
+    # Safety
+
+    This function is safe as long as the MatchingRuleResult pointer is a valid
+    pointer and the iterator has not been deleted.
     """
     raise NotImplementedError
 
 
-def pact_message_iter_delete(iter: PactMessageIterator) -> None:
+def validate_datetime(value: str, format: str) -> None:
     """
-    Free the iterator when you're done using it.
+    Validates the date/time value against the date/time format string.
 
     [Rust
-    `pactffi_pact_message_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_message_iter_delete)
+    `pactffi_validate_datetime`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_validate_datetime)
+
+    Raises:
+        ValueError:
+            If the value is not a valid date/time for the format string.
+
+        RuntimeError:
+            For any other error.
     """
-    lib.pactffi_pact_message_iter_delete(iter._ptr)
+    ret = lib.pactffi_validate_datetime(value.encode(), format.encode())
+    if ret == 0:
+        return
+    if ret == 1:
+        msg = f"Invalid datetime value {value!r}' for format {format!r}"
+        raise ValueError(msg)
+    if ret == 2:  # noqa: PLR2004
+        msg = f"Panic while validating datetime value: {get_error_message()}"
+    else:
+        msg = f"Unknown error while validating datetime value: {ret}"
+    raise RuntimeError(msg)
 
 
-def pact_message_iter_next(iter: PactMessageIterator) -> Message:
+def generator_to_json(generator: Generator) -> str:
     """
-    Get the next message from the message pact.
+    Get the JSON form of the generator.
 
     [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)
+    `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`.
 
-def pact_sync_message_iter_next(iter: PactSyncMessageIterator) -> SynchronousMessage:
-    """
-    Get the next synchronous request/response message from the V4 pact.
+    # Safety
 
-    [Rust
-    `pactffi_pact_sync_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_sync_message_iter_next)
+    This function will fail if it is passed a NULL pointer, or the owner of the
+    generator has been deleted.
     """
-    ptr = lib.pactffi_pact_sync_message_iter_next(iter._ptr)
-    if ptr == ffi.NULL:
-        raise StopIteration
-    raise NotImplementedError
-    return SynchronousMessage(ptr)
+    return OwnedString(lib.pactffi_generator_to_json(generator._ptr))
 
 
-def pact_sync_message_iter_delete(iter: PactSyncMessageIterator) -> None:
+def generator_generate_string(generator: Generator, context_json: str) -> str:
     """
-    Free the iterator when you're done using it.
+    Generate a string value using the provided generator.
+
+    An optional JSON payload containing any generator context ca be given. The
+    context value is used for generators like `MockServerURL` (which should
+    contain details about the running mock server) and `ProviderStateGenerator`
+    (which should be the values returned from the Provider State callback
+    function).
 
     [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_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.
     """
-    lib.pactffi_pact_sync_message_iter_delete(iter._ptr)
+    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 pact_sync_http_iter_next(iter: PactSyncHttpIterator) -> SynchronousHttp:
+def generator_generate_integer(generator: Generator, context_json: str) -> int:
     """
-    Get the next synchronous HTTP request/response interaction from the V4 pact.
+    Generate an integer value using the provided generator.
+
+    An optional JSON payload containing any generator context can be given. The
+    context value is used for generators like `ProviderStateGenerator` (which
+    should be the values returned from the Provider State callback function).
 
     [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_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.
     """
-    ptr = lib.pactffi_pact_sync_http_iter_next(iter._ptr)
-    if ptr == ffi.NULL:
-        raise StopIteration
-    raise NotImplementedError
-    return SynchronousHttp(ptr)
+    return lib.pactffi_generator_generate_integer(
+        generator._ptr,
+        context_json.encode("utf-8"),
+    )
 
 
-def pact_sync_http_iter_delete(iter: PactSyncHttpIterator) -> None:
+def generators_iter_delete(iter: GeneratorCategoryIterator) -> 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_generators_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generators_iter_delete)
     """
-    lib.pactffi_pact_sync_http_iter_delete(iter._ptr)
+    lib.pactffi_generators_iter_delete(iter._ptr)
 
 
-def pact_interaction_iter_next(iter: PactInteractionIterator) -> PactInteraction:
+def generators_iter_next(iter: GeneratorCategoryIterator) -> GeneratorKeyValuePair:
     """
-    Get the next interaction from the pact.
+    Get the next path and generator out of the iterator, if possible.
 
     [Rust
-    `pactffi_pact_interaction_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_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`.
+
+    Raises:
+        StopIteration:
+            If the iterator has reached the end.
     """
-    ptr = lib.pactffi_pact_interaction_iter_next(iter._ptr)
+    ptr = lib.pactffi_generators_iter_next(iter._ptr)
     if ptr == ffi.NULL:
         raise StopIteration
-    raise NotImplementedError
-    return PactInteraction(ptr)
+    return GeneratorKeyValuePair(ptr)
 
 
-def pact_interaction_iter_delete(iter: PactInteractionIterator) -> None:
+def generators_iter_pair_delete(pair: GeneratorKeyValuePair) -> None:
     """
-    Free the iterator when you're done using it.
+    Free a pair of key and value returned from `pactffi_generators_iter_next`.
 
     [Rust
-    `pactffi_pact_interaction_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_interaction_iter_delete)
+    `pactffi_generators_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_generators_iter_pair_delete)
     """
-    lib.pactffi_pact_interaction_iter_delete(iter._ptr)
+    lib.pactffi_generators_iter_pair_delete(pair._ptr)
 
 
-def matching_rule_to_json(rule: MatchingRule) -> str:
+def sync_http_new() -> SynchronousHttp:
     """
-    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)
+    Get a mutable pointer to a newly-created default interaction on the heap.
 
-    The returned string must be deleted with `pactffi_string_delete`.
+    [Rust `pactffi_sync_http_new`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_new)
 
     # Safety
 
-    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.
+    This function is safe.
+
+    # Error Handling
+
+    Returns NULL on error.
     """
     raise NotImplementedError
 
 
-def matching_rules_iter_delete(iter: MatchingRuleCategoryIterator) -> None:
+def sync_http_delete(interaction: SynchronousHttp) -> None:
     """
-    Free the iterator when you're done using it.
+    Destroy the `SynchronousHttp` interaction being pointed to.
 
     [Rust
-    `pactffi_matching_rules_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rules_iter_delete)
+    `pactffi_sync_http_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_sync_http_delete(interaction)
 
 
-def matching_rules_iter_next(
-    iter: MatchingRuleCategoryIterator,
-) -> MatchingRuleKeyValuePair:
+def sync_http_get_request(interaction: SynchronousHttp) -> HttpRequest:
     """
-    Get the next path and matching rule out of the iterator, if possible.
+    Get the request of a `SynchronousHttp` interaction.
 
     [Rust
-    `pactffi_matching_rules_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_matching_rules_iter_next)
-
-    The returned pointer must be deleted with
-    `pactffi_matching_rules_iter_pair_delete`.
+    `pactffi_sync_http_get_request`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_request)
 
     # Safety
 
-    The underlying data is owned by the `MatchingRuleKeyValuePair`, so is always
-    safe to use.
+    The data pointed to by the pointer this function returns will be deleted
+    when the interaction is deleted. Trying to use if after the interaction is
+    deleted will result in undefined behaviour.
 
     # Error Handling
 
-    If no further data is present, returns NULL.
+    If the interaction is NULL, returns NULL.
     """
     raise NotImplementedError
 
 
-def matching_rules_iter_pair_delete(pair: MatchingRuleKeyValuePair) -> None:
+def sync_http_get_request_contents(interaction: SynchronousHttp) -> str | None:
     """
-    Free a pair of key and value returned from `message_metadata_iter_next`.
+    Get the request contents of a `SynchronousHttp` interaction in string form.
 
     [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_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`.
     """
-    raise NotImplementedError
+    ptr = lib.pactffi_sync_http_get_request_contents(interaction._ptr)
+    if ptr == ffi.NULL:
+        return None
+    return OwnedString(ptr)
 
 
-def message_new() -> Message:
+def sync_http_set_request_contents(
+    interaction: SynchronousHttp,
+    contents: str,
+    content_type: str,
+) -> None:
     """
-    Get a mutable pointer to a newly-created default message on the heap.
+    Sets the request contents of the interaction.
 
     [Rust
-    `pactffi_message_new`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_new)
+    `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
+      NULL-terminated UTF-8 string pointer.
+    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
+      the content type of the data.
 
     # Safety
 
-    This function is safe.
+    The request 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
 
-    Returns NULL on error.
+    If the contents is a NULL pointer, it will set the request 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_new_from_json(
-    index: int,
-    json_str: str,
-    spec_version: PactSpecification,
-) -> Message:
+def sync_http_get_request_contents_length(interaction: SynchronousHttp) -> int:
     """
-    Constructs a `Message` from the JSON string.
+    Get the length of the request contents of a `SynchronousHttp` interaction.
 
     [Rust
-    `pactffi_message_new_from_json`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_new_from_json)
+    `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)
 
-    # Safety
+    This function will return 0 if the body is missing.
+    """
+    return lib.pactffi_sync_http_get_request_contents_length(interaction._ptr)
 
-    This function is safe.
 
-    # Error Handling
+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.21/pact_ffi/?search=pactffi_sync_http_get_request_contents_bin)
 
-    If the JSON string is invalid or not UTF-8 encoded, returns a 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 message_new_from_body(body: str, content_type: str) -> Message:
+def sync_http_set_request_contents_bin(
+    interaction: SynchronousHttp,
+    contents: str,
+    len: int,
+    content_type: str,
+) -> None:
     """
-    Constructs a `Message` from a body with a given content-type.
+    Sets the request contents of the interaction as an array of bytes.
 
     [Rust
-    `pactffi_message_new_from_body`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_new_from_body)
+    `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
+    - `len` - number of bytes to copy from the contents pointer
+    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
+      the content type of the data.
 
     # Safety
 
-    This function is safe.
+    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 body or content type are invalid or not UTF-8 encoded, returns NULL.
+    If the contents is a NULL pointer, it will set the request 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_delete(message: Message) -> None:
+def sync_http_get_response(interaction: SynchronousHttp) -> HttpResponse:
     """
-    Destroy the `Message` being pointed to.
+    Get the response of a `SynchronousHttp` interaction.
 
     [Rust
-    `pactffi_message_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_delete)
-    """
-    raise NotImplementedError
+    `pactffi_sync_http_get_response`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_response)
 
+    # Safety
 
-def message_get_contents(message: Message) -> OwnedString | None:
-    """
-    Get the contents of a `Message` in string form.
+    The data pointed to by the pointer this function returns will be deleted
+    when the interaction is deleted. Trying to use if after the interaction is
+    deleted will result in undefined behaviour.
 
-    [Rust
-    `pactffi_message_get_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_contents)
+    # Error Handling
 
-    # Safety
+    If the interaction is NULL, returns NULL.
+    """
+    raise NotImplementedError
 
-    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.
+def sync_http_get_response_contents(interaction: SynchronousHttp) -> str | None:
+    """
+    Get the response contents of a `SynchronousHttp` interaction in string form.
 
-    # Error Handling
+    [Rust
+    `pactffi_sync_http_get_response_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_response_contents)
 
-    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.
+    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 message_set_contents(message: Message, contents: str, content_type: str) -> None:
+def sync_http_set_response_contents(
+    interaction: SynchronousHttp,
+    contents: str,
+    content_type: str,
+) -> None:
     """
-    Sets the contents of the message.
+    Sets the response contents of the interaction.
 
     [Rust
-    `pactffi_message_set_contents`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_set_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
+      NULL-terminated UTF-8 string pointer.
+    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
+      the content type of the data.
 
     # 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
+    The response 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.
+    If the contents is a NULL pointer, it will set the response 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:
+def sync_http_get_response_contents_length(interaction: SynchronousHttp) -> int:
     """
-    Get the length of the contents of a `Message`.
+    Get the length of the response contents of a `SynchronousHttp` interaction.
 
     [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
+    `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)
 
-    If the message is NULL, returns 0. If the body of the message 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 message_get_contents_bin(message: Message) -> str:
+def sync_http_get_response_contents_bin(interaction: SynchronousHttp) -> bytes | None:
     """
-    Get the contents of a `Message` as a pointer to an array of bytes.
+    Get the response contents of a `SynchronousHttp` interaction as bytes.
 
     [Rust
-    `pactffi_message_get_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_get_contents_bin)
-
-    # Safety
+    `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)
 
-    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.
+    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 message_set_contents_bin(
-    message: Message,
+def sync_http_set_response_contents_bin(
+    interaction: SynchronousHttp,
     contents: str,
     len: int,
     content_type: str,
 ) -> None:
     """
-    Sets the contents of the message as an array of bytes.
+    Sets the response contents of the `SynchronousHttp` interaction as bytes.
 
     [Rust
-    `pactffi_message_set_contents_bin`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_set_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
+    - `len` - number of bytes to copy
+    - `content_type` - pointer to the NULL-terminated UTF-8 string containing
+      the content type of the data.
 
     # Safety
 
@@ -3346,42 +3767,37 @@ def message_set_contents_bin(
 
     # 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.
+    If the contents is a NULL pointer, it will set the response 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:
+def sync_http_get_description(interaction: SynchronousHttp) -> str:
     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.
+    `pactffi_sync_http_get_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_get_description)
 
-    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 message_set_description(message: Message, description: str) -> int:
+def sync_http_set_description(interaction: SynchronousHttp, description: str) -> int:
     """
-    Write the `description` field on the `Message`.
+    Write the `description` field on the `SynchronousHttp`.
 
     [Rust
-    `pactffi_message_set_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_set_description)
+    `pactffi_sync_http_set_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_http_set_description)
 
     # Safety
 
@@ -3398,18 +3814,22 @@ def message_set_description(message: Message, description: str) -> int:
     raise NotImplementedError
 
 
-def message_get_provider_state(message: Message, index: int) -> ProviderState:
+def sync_http_get_provider_state(
+    interaction: SynchronousHttp,
+    index: int,
+) -> ProviderState:
     r"""
-    Get a copy of the provider state at the given index from this message.
+    Get a copy of the provider state at the given index from this interaction.
 
     [Rust
-    `pactffi_message_get_provider_state`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_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
 
     The returned structure must be deleted with `provider_state_delete`.
 
-    Since it is a copy, the returned structure may safely outlive the `Message`.
+    Since it is a copy, the returned structure may safely outlive the
+    `SynchronousHttp`.
 
     # Error Handling
 
@@ -3421,390 +3841,344 @@ def message_get_provider_state(message: Message, index: int) -> ProviderState:
     raise NotImplementedError
 
 
-def message_get_provider_state_iter(message: Message) -> ProviderStateIterator:
+def sync_http_get_provider_state_iter(
+    interaction: SynchronousHttp,
+) -> 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)
+    `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
 
     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:
+    Raises:
+        RuntimeError:
+            If the iterator cannot be retrieved
     """
-    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)
-
-    # Safety
-
-    The underlying data must not change during iteration.
+    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)
 
-    If a previous call panicked, then the internal mutex will have been poisoned
-    and this function will return NULL.
 
-    # Error Handling
+def pact_interaction_as_synchronous_http(
+    interaction: PactInteraction,
+) -> SynchronousHttp:
+    r"""
+    Casts this interaction to a `SynchronousHttp` interaction.
 
-    Returns NULL if an error occurs.
-    """
-    raise NotImplementedError
+    Returns a NULL pointer if the interaction can not be casted to a
+    `SynchronousHttp` interaction (for instance, it is a message interaction).
+    The returned pointer must be freed with `pactffi_sync_http_delete` when no
+    longer required.
 
+    [Rust
+    `pactffi_pact_interaction_as_synchronous_http`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_as_synchronous_http)
 
-def provider_state_iter_delete(iter: ProviderStateIterator) -> None:
-    """
-    Delete the iterator.
+    # Safety This function is safe as long as the interaction pointer is a valid
+    pointer.
 
-    [Rust
-    `pactffi_provider_state_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_provider_state_iter_delete)
+    # Errors On any error, this function will return a NULL pointer.
     """
     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.
+def pact_interaction_as_asynchronous_message(
+    interaction: PactInteraction,
+) -> AsynchronousMessage:
     """
-    raise NotImplementedError
-
+    Casts this interaction to a `AsynchronousMessage` interaction.
 
-def message_insert_metadata(message: Message, key: str, value: str) -> int:
-    r"""
-    Insert the (`key`, `value`) pair into this Message's `metadata` HashMap.
+    Returns a NULL pointer if the interaction can not be casted to a
+    `AsynchronousMessage` interaction (for instance, it is a http interaction).
+    The returned pointer must be freed with `pactffi_async_message_delete` when
+    no longer required.
 
     [Rust
-    `pactffi_message_insert_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_insert_metadata)
-
-    # Safety
+    `pactffi_pact_interaction_as_asynchronous_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_as_asynchronous_message)
 
-    This function returns an enum indicating the result; see the comments on
-    HashMapInsertStatus for details.
+    Note that if the interaction is a V3 `Message`, it will be converted to a V4
+    `AsynchronousMessage` before being returned.
 
-    # Error Handling
+    # Safety This function is safe as long as the interaction pointer is a valid
+    pointer.
 
-    This function may fail if the provided `key` or `value` strings contain
-    invalid UTF-8.
+    # Errors On any error, this function will return a NULL pointer.
     """
     raise NotImplementedError
 
 
-def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadataPair:
-    """
-    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)
-
-    The returned pointer must be deleted with
-    `pactffi_message_metadata_pair_delete`.
-
-    # Safety
-
-    The underlying data must not change during iteration. 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.
-
-    # Error Handling
-
-    If no further data is present, returns NULL.
+def pact_interaction_as_synchronous_message(
+    interaction: PactInteraction,
+) -> SynchronousMessage:
     """
-    raise NotImplementedError
-
+    Casts this interaction to a `SynchronousMessage` interaction.
 
-def message_get_metadata_iter(message: Message) -> MessageMetadataIterator:
-    r"""
-    Get an iterator over the metadata of a message.
+    Returns a NULL pointer if the interaction can not be casted to a
+    `SynchronousMessage` interaction (for instance, it is a http interaction).
+    The returned pointer must be freed with `pactffi_sync_message_delete` when
+    no longer required.
 
     [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
+    `pactffi_pact_interaction_as_synchronous_message`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_as_synchronous_message)
 
-    On failure, this function will return a NULL pointer.
+    # Safety This function is safe as long as the interaction pointer is a valid
+    pointer.
 
-    This function may fail if any of the Rust strings contain embedded null
-    ('\0') bytes.
+    # Errors On any error, this function will return a NULL pointer.
     """
     raise NotImplementedError
 
 
-def message_metadata_iter_delete(iter: MessageMetadataIterator) -> None:
+def pact_async_message_iter_next(iter: PactAsyncMessageIterator) -> AsynchronousMessage:
     """
-    Free the metadata iterator when you're done using it.
+    Get the next asynchronous message from the iterator.
 
     [Rust
-    `pactffi_message_metadata_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_metadata_iter_delete)
+    `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:
+            If the iterator has reached the end.
     """
-    raise NotImplementedError
+    ptr = lib.pactffi_pact_async_message_iter_next(iter._ptr)
+    if ptr == ffi.NULL:
+        raise StopIteration
+    return AsynchronousMessage(ptr, owned=True)
 
 
-def message_metadata_pair_delete(pair: MessageMetadataPair) -> None:
+def pact_async_message_iter_delete(iter: PactAsyncMessageIterator) -> None:
     """
-    Free a pair of key and value returned from `message_metadata_iter_next`.
+    Free the iterator when you're done using it.
 
     [Rust
-    `pactffi_message_metadata_pair_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_metadata_pair_delete)
+    `pactffi_pact_async_message_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_async_message_iter_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_pact_async_message_iter_delete(iter._ptr)
 
 
-def message_pact_new_from_json(file_name: str, json_str: str) -> MessagePact:
+def pact_sync_message_iter_next(iter: PactSyncMessageIterator) -> SynchronousMessage:
     """
-    Construct a new `MessagePact` from the JSON string.
-
-    The provided file name is used when generating error messages.
+    Get the next synchronous request/response message from the V4 pact.
 
     [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.
+    `pactffi_pact_sync_message_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_message_iter_next)
 
-    # Error Handling
-
-    On error, this function will return a null pointer.
+    Raises:
+        StopIteration:
+            If the iterator has reached the end.
     """
-    raise NotImplementedError
+    ptr = lib.pactffi_pact_sync_message_iter_next(iter._ptr)
+    if ptr == ffi.NULL:
+        raise StopIteration
+    return SynchronousMessage(ptr, owned=True)
 
 
-def message_pact_delete(message_pact: MessagePact) -> None:
+def pact_sync_message_iter_delete(iter: PactSyncMessageIterator) -> None:
     """
-    Delete the `MessagePact` being pointed to.
+    Free the iterator when you're done using it.
 
     [Rust
-    `pactffi_message_pact_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_delete)
+    `pactffi_pact_sync_message_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_message_iter_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_pact_sync_message_iter_delete(iter._ptr)
 
 
-def message_pact_get_consumer(message_pact: MessagePact) -> Consumer:
+def pact_sync_http_iter_next(iter: PactSyncHttpIterator) -> SynchronousHttp:
     """
-    Get a pointer to the Consumer struct inside the MessagePact.
-
-    This is a mutable borrow: The caller may mutate the Consumer through this
-    pointer.
+    Get the next synchronous HTTP request/response interaction from the V4 pact.
 
     [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
+    `pactffi_pact_sync_http_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_http_iter_next)
 
-    This function will only fail if it is passed a NULL pointer. In the case of
-    error, a NULL pointer will be returned.
+    Raises:
+        StopIteration:
+            If the iterator has reached the end.
     """
-    raise NotImplementedError
+    ptr = lib.pactffi_pact_sync_http_iter_next(iter._ptr)
+    if ptr == ffi.NULL:
+        raise StopIteration
+    return SynchronousHttp(ptr, owned=True)
 
 
-def message_pact_get_provider(message_pact: MessagePact) -> Provider:
+def pact_sync_http_iter_delete(iter: PactSyncHttpIterator) -> None:
     """
-    Get a pointer to the Provider struct inside the MessagePact.
-
-    This is a mutable borrow: The caller may mutate the Provider through this
-    pointer.
+    Free the iterator when you're done using it.
 
     [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.
+    `pactffi_pact_sync_http_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_sync_http_iter_delete)
     """
-    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.
+    lib.pactffi_pact_sync_http_iter_delete(iter._ptr)
 
-    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
+def pact_interaction_iter_next(iter: PactInteractionIterator) -> PactInteraction:
+    """
+    Get the next interaction from the pact.
 
-    On failure, this function will return a NULL pointer.
+    [Rust
+    `pactffi_pact_interaction_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_pact_interaction_iter_next)
 
-    This function may fail if any of the Rust strings contain embedded null
-    ('\0') bytes.
+    Raises:
+        StopIteration:
+            If the iterator has reached the end.
     """
+    ptr = lib.pactffi_pact_interaction_iter_next(iter._ptr)
+    if ptr == ffi.NULL:
+        raise StopIteration
     raise NotImplementedError
+    return PactInteraction(ptr)
 
 
-def message_pact_message_iter_next(iter: MessagePactMessageIterator) -> Message:
+def pact_interaction_iter_delete(iter: PactInteractionIterator) -> None:
     """
-    Get the next message from the message pact.
+    Free the iterator when you're done using it.
 
     [Rust
-    `pactffi_message_pact_message_iter_next`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_message_iter_next)
+    `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)
 
-    # Safety
 
-    This function is safe.
+def matching_rule_to_json(rule: MatchingRule) -> str:
+    """
+    Get the JSON form of the matching rule.
 
-    # Error Handling
+    [Rust
+    `pactffi_matching_rule_to_json`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rule_to_json)
 
-    This function will return a NULL pointer if passed a NULL pointer or if an
-    error occurs.
+    The returned string must be deleted with `pactffi_string_delete`.
+
+    # Safety
+
+    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 message_pact_message_iter_delete(iter: MessagePactMessageIterator) -> None:
+def matching_rules_iter_delete(iter: MatchingRuleCategoryIterator) -> None:
     """
-    Delete the iterator.
+    Free the iterator when you're done using it.
 
     [Rust
-    `pactffi_message_pact_message_iter_delete`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_message_iter_delete)
+    `pactffi_matching_rules_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rules_iter_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_matching_rules_iter_delete(iter._ptr)
 
 
-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`.
+def matching_rules_iter_next(
+    iter: MatchingRuleCategoryIterator,
+) -> MatchingRuleKeyValuePair:
+    """
+    Get the next path and matching rule out of the iterator, if possible.
 
     [Rust
-    `pactffi_message_pact_find_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_find_metadata)
+    `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`.
 
     # Safety
 
-    Since it is a copy, the returned string may safely outlive the `Message`.
+    The underlying data is owned by the `MatchingRuleKeyValuePair`, so is always
+    safe to use.
 
-    The returned string must be deleted with `pactffi_string_delete`.
+    # Error Handling
 
-    The returned pointer will be NULL if the metadata does not contain the given
-    key, or if an error occurred.
+    If no further data is present, returns NULL.
+    """
+    return MatchingRuleKeyValuePair(lib.pactffi_matching_rules_iter_next(iter._ptr))
 
-    # Error Handling
 
-    On failure, this function will return a NULL pointer.
+def matching_rules_iter_pair_delete(pair: MatchingRuleKeyValuePair) -> None:
+    """
+    Free a pair of key and value returned from `message_metadata_iter_next`.
 
-    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.
+    [Rust
+    `pactffi_matching_rules_iter_pair_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_matching_rules_iter_pair_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_matching_rules_iter_pair_delete(pair._ptr)
 
 
-def message_pact_get_metadata_iter(
-    message_pact: MessagePact,
-) -> MessagePactMetadataIterator:
-    r"""
-    Get an iterator over the metadata of a message pact.
+def provider_state_iter_next(iter: ProviderStateIterator) -> ProviderState:
+    """
+    Get the next value from the iterator.
 
     [Rust
-    `pactffi_message_pact_get_metadata_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_message_pact_get_metadata_iter)
+    `pactffi_provider_state_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_iter_next)
 
     # Safety
 
-    This iterator carries a pointer to the message pact, and must not outlive
-    the message pact.
+    The underlying data must not change during iteration.
 
-    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.
+    Raises:
+        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:
+        raise StopIteration
+    return ProviderState(provider_state)
 
-    # Error Handling
 
-    On failure, this function will return a NULL pointer.
+def provider_state_iter_delete(iter: ProviderStateIterator) -> None:
+    """
+    Delete the iterator.
 
-    This function may fail if any of the Rust strings contain embedded null
-    ('\0') bytes.
+    [Rust
+    `pactffi_provider_state_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_iter_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_provider_state_iter_delete(iter._ptr)
 
 
-def message_pact_metadata_iter_next(
-    iter: MessagePactMetadataIterator,
-) -> MessagePactMetadataTriple:
+def message_metadata_iter_next(iter: MessageMetadataIterator) -> MessageMetadataPair:
     """
-    Get the next triple out of the iterator, if possible.
+    Get the next key and value 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)
+    `pactffi_message_metadata_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_metadata_iter_next)
 
-    # Safety
+    The returned pointer must be deleted with
+    `pactffi_message_metadata_pair_delete`.
 
-    This operation is invalid if the underlying data has been changed during
-    iteration.
+    # Safety
 
-    # Error Handling
+    The underlying data must not change during iteration. 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.
 
-    Returns null if no next element is present.
+    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_pact_metadata_iter_delete(iter: MessagePactMetadataIterator) -> None:
+def message_metadata_iter_delete(iter: MessageMetadataIterator) -> 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)
+    [Rust
+    `pactffi_message_metadata_iter_delete`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_message_metadata_iter_delete)
     """
-    raise NotImplementedError
+    lib.pactffi_message_metadata_iter_delete(iter._ptr)
 
 
-def message_pact_metadata_triple_delete(triple: MessagePactMetadataTriple) -> None:
+def message_metadata_pair_delete(pair: MessageMetadataPair) -> None:
     """
-    Free a triple returned from `pactffi_message_pact_metadata_iter_next`.
+    Free a pair of key and value returned from `message_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)
+    [Rust
+    `pactffi_message_metadata_pair_delete`](https://docs.rs/pact_ffi/0.4.21/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:
@@ -3812,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`.
 
@@ -3858,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
 
@@ -3873,29 +4247,27 @@ 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
 
 
-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.
+    `pactffi_provider_state_get_name`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_get_name)
 
-    # 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(
@@ -3905,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
 
@@ -3915,14 +4287,15 @@ 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(
@@ -3932,22 +4305,20 @@ 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)
-
-    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.
+    `pactffi_provider_state_param_iter_next`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_provider_state_param_iter_next)
 
     # 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:
@@ -3955,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
 
@@ -3965,9 +4336,9 @@ 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)
     """
-    raise NotImplementedError
+    lib.pactffi_provider_state_param_iter_delete(iter._ptr)
 
 
 def provider_state_param_pair_delete(pair: ProviderStateParamPair) -> None:
@@ -3975,9 +4346,9 @@ 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)
     """
-    raise NotImplementedError
+    lib.pactffi_provider_state_param_pair_delete(pair._ptr)
 
 
 def sync_message_new() -> SynchronousMessage:
@@ -3985,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
 
@@ -4003,9 +4374,9 @@ 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)
     """
-    raise NotImplementedError
+    lib.pactffi_sync_message_delete(message._ptr)
 
 
 def sync_message_get_request_contents_str(message: SynchronousMessage) -> str:
@@ -4013,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
 
@@ -4040,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
@@ -4068,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
 
@@ -4087,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
 
@@ -4114,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
@@ -4141,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
 
@@ -4156,22 +4527,41 @@ def sync_message_get_request_contents(message: SynchronousMessage) -> MessageCon
     raise NotImplementedError
 
 
-def sync_message_get_number_responses(message: SynchronousMessage) -> int:
+def sync_message_generate_request_contents(
+    message: SynchronousMessage,
+) -> MessageContents:
     """
-    Get the number of response messages in the `SynchronousMessage`.
+    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_get_number_responses`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_number_responses)
+    `pactffi_sync_message_generate_request_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_generate_request_contents)
 
-    # Safety
+    Raises:
+        RuntimeError:
+            If the request contents cannot be generated
+    """
+    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)
 
-    The message pointer must point to a valid SynchronousMessage.
 
-    # Error Handling
+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.21/pact_ffi/?search=pactffi_sync_message_get_number_responses)
 
-    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(
@@ -4182,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
 
@@ -4215,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.
@@ -4247,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
 
@@ -4269,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
 
@@ -4300,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
@@ -4331,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
 
@@ -4346,28 +4736,49 @@ def sync_message_get_response_contents(
     raise NotImplementedError
 
 
-def sync_message_get_description(message: SynchronousMessage) -> str:
-    r"""
-    Get a copy of the description.
+def sync_message_generate_response_contents(
+    message: SynchronousMessage,
+    index: int,
+) -> MessageContents:
+    """
+    Get the response contents of an `SynchronousMessage` as a `MessageContents`.
 
-    [Rust
-    `pactffi_sync_message_get_description`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_sync_message_get_description)
+    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.
 
-    # Safety
+    [Rust
+    `pactffi_sync_message_generate_response_contents`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_generate_response_contents)
 
-    The returned string must be deleted with `pactffi_string_delete`.
+    Raises:
+        RuntimeError:
+            If the response contents could not be generated.
+    """
+    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)
 
-    Since it is a copy, the returned string may safely outlive the
-    `SynchronousMessage`.
 
-    # Errors
+def sync_message_get_description(message: SynchronousMessage) -> str:
+    r"""
+    Get a copy of the description.
 
-    On failure, this function will return a NULL pointer.
+    [Rust
+    `pactffi_sync_message_get_description`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_sync_message_get_description)
 
-    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:
@@ -4375,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
 
@@ -4400,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
 
@@ -4426,17 +4837,21 @@ 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
 
     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:
@@ -4444,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)
 
@@ -4459,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)
@@ -4495,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.
@@ -4516,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`.
@@ -4559,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:
@@ -4584,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,
@@ -4620,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)
 
@@ -4632,13 +5048,14 @@ 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
 
     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:
@@ -4658,14 +5075,15 @@ 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:
             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:
@@ -4686,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:
@@ -4700,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,
@@ -4737,10 +5156,11 @@ 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: 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:
@@ -4760,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
 
@@ -4777,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
 
@@ -4796,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
 
@@ -4811,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.
 
@@ -4833,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:
@@ -4874,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:
@@ -4902,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:
@@ -4933,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:
@@ -4958,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
 
@@ -4970,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:
@@ -4995,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:
@@ -5005,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:
@@ -5021,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:
@@ -5030,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,
@@ -5081,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:
@@ -5097,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,
@@ -5122,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:
@@ -5135,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,
@@ -5172,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:
@@ -5186,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
@@ -5198,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,
@@ -5220,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:
@@ -5257,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.
@@ -5275,6 +5678,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.
@@ -5291,10 +5712,11 @@ 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: 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,
@@ -5312,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:
@@ -5320,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:
@@ -5332,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:
@@ -5344,43 +5770,119 @@ def handle_get_pact_spec_version(handle: PactHandle) -> PactSpecification:
     return PactSpecification(lib.pactffi_handle_get_pact_spec_version(handle._ref))
 
 
-def with_pact_metadata(
-    pact: PactHandle,
-    namespace: str,
-    name: str,
-    value: str,
-) -> None:
-    """
-    Sets the additional metadata on the Pact file.
+def with_pact_metadata(
+    pact: PactHandle,
+    namespace: str,
+    name: str,
+    value: str,
+) -> None:
+    """
+    Sets the additional metadata on the Pact file.
+
+    Common uses are to add the client library details such as the name and
+    version Returns false if the interaction or Pact can't be modified (i.e. the
+    mock server for it has already started)
+
+    [Rust
+    `pactffi_with_pact_metadata`](https://docs.rs/pact_ffi/0.4.21/pact_ffi/?search=pactffi_with_pact_metadata)
+
+    Args:
+        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
+
+    Raises:
+        RuntimeError:
+            If the metadata could not be set.
+    """
+    success: bool = lib.pactffi_with_pact_metadata(
+        pact._ref,
+        namespace.encode("utf-8"),
+        name.encode("utf-8"),
+        value.encode("utf-8"),
+    )
+    if not success:
+        msg = f"Failed to set Pact metadata for {pact} with {namespace}.{name}={value}"
+        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",
+        }),
+    )
+    ```
 
-    Common uses are to add the client library details such as the name and
-    version Returns false if the interaction or Pact can't be modified (i.e. the
-    mock server for it has already started)
+    See
+    [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.21/rust/pact_ffi/IntegrationJson.md)
 
-    [Rust
-    `pactffi_with_pact_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_pact_metadata)
+    # Note
 
-    Args:
-        pact:
-            Handle to a Pact model
+    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`.
 
-        namespace:
-            The top level metadat key to set any key values on
+    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.
 
-        name:
-            The key to set
+    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.
 
-        value:
-            The value to set
+    # 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.
+
+    Raises:
+        RuntimeError:
+            If the metadata could not be set.
     """
-    success: bool = lib.pactffi_with_pact_metadata(
-        pact._ref,
-        namespace.encode("utf-8"),
-        name.encode("utf-8"),
+    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 Pact metadata for {pact} with {namespace}.{name}={value}"
+        msg = f"Failed to set metadata for {interaction} with {key}={value}"
         raise RuntimeError(msg)
 
 
@@ -5394,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:
@@ -5432,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:
@@ -5454,10 +5956,11 @@ 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: 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,
@@ -5485,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`.
 
@@ -5504,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,
@@ -5522,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:
@@ -5532,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:
@@ -5545,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
@@ -5564,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:
@@ -5575,10 +6080,11 @@ 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: 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")
@@ -5598,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
@@ -5619,10 +6125,11 @@ 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: If the body could not be specified.
+        RuntimeError:
+            If the body could not be specified.
     """
     success: bool = lib.pactffi_with_body(
         interaction._ref,
@@ -5645,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
@@ -5669,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
 
@@ -5688,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
@@ -5709,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(
@@ -5738,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.
@@ -5754,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,
@@ -5766,6 +6279,47 @@ 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.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).
+
+    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.
+
+    Raises:
+        RuntimeError:
+            If the generators could not be added.
+    """
+    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,
@@ -5782,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.
@@ -5836,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.
@@ -5873,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:
@@ -5882,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,
@@ -5897,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:
@@ -5905,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:
@@ -5917,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:
@@ -5933,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,
@@ -5950,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:
@@ -5958,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,
@@ -5968,12 +6535,15 @@ def add_text_comment(interaction: InteractionHandle, comment: str) -> None:
         raise RuntimeError(msg)
 
 
-def pact_handle_get_message_iter(pact: PactHandle) -> PactMessageIterator:
+def pact_handle_get_async_message_iter(pact: PactHandle) -> PactAsyncMessageIterator:
     r"""
-    Get an iterator over all the 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`.
 
     [Rust
-    `pactffi_pact_handle_get_message_iter`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_pact_handle_get_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
 
@@ -5986,7 +6556,9 @@ def pact_handle_get_message_iter(pact: PactHandle) -> PactMessageIterator:
     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))
+    return PactAsyncMessageIterator(
+        lib.pactffi_pact_handle_get_async_message_iter(pact._ref),
+    )
 
 
 def pact_handle_get_sync_message_iter(pact: PactHandle) -> PactSyncMessageIterator:
@@ -5997,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
 
@@ -6023,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
 
@@ -6039,249 +6611,6 @@ def pact_handle_get_sync_http_iter(pact: PactHandle) -> PactSyncHttpIterator:
     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/.*",
-        }),
-    )
-    ```
-
-    See [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/libpact_ffi-v0.4.19/rust/pact_ffi/IntegrationJson.md).
-    """
-    raise NotImplementedError
-
-
-def message_reify(message_handle: MessageHandle) -> OwnedString:
-    """
-    Reifies the given message.
-
-    [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.
-
-    # 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.
-
-    Common uses are to add the client library details such as the name and
-    version
-
-    [Rust
-    `pactffi_with_message_pact_metadata`](https://docs.rs/pact_ffi/0.4.19/pact_ffi/?search=pactffi_with_message_pact_metadata)
-
-    * `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
-    """
-    raise NotImplementedError
-
-
 def pact_handle_write_file(
     pact: PactHandle,
     directory: Path | str | None,
@@ -6292,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.
 
@@ -6308,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,
@@ -6332,10 +6665,11 @@ 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: 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:
@@ -6347,29 +6681,11 @@ 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.
 
-    [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
 
@@ -6396,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__
 
@@ -6411,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)
 
@@ -6428,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:
@@ -6474,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:
@@ -6515,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.
 
@@ -6551,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:
@@ -6586,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:
@@ -6597,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,
@@ -6617,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.
 
@@ -6627,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,
@@ -6642,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:
@@ -6650,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,
@@ -6671,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:
@@ -6688,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,
@@ -6710,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,
@@ -6728,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,
@@ -6742,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"))
 
@@ -6754,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
 
@@ -6776,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:
@@ -6816,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.
@@ -6864,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
@@ -6912,9 +7244,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,
@@ -6929,7 +7263,11 @@ 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:
+            If the verifier could not be executed.
     """
     success: int = lib.pactffi_verifier_execute(handle._ref)
     if success != 0:
@@ -6945,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
@@ -7001,11 +7339,15 @@ 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`
     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:
@@ -7019,11 +7361,15 @@ 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`
     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:
@@ -7037,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:
@@ -7047,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:
@@ -7060,7 +7410,11 @@ 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:
+            If the JSON could not be extracted.
     """
     ptr = lib.pactffi_verifier_json(handle._ref)
     if ptr == ffi.NULL:
@@ -7084,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:
@@ -7096,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,
@@ -7123,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)
 
@@ -7142,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:
@@ -7157,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,
@@ -7198,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
@@ -7229,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
@@ -7259,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
@@ -7289,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
@@ -7319,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
@@ -7351,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
@@ -7386,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/_async_message_interaction.py b/src/pact/v3/interaction/_async_message_interaction.py
index 011853af39..15621650a9 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 1b375d29a8..9696f37a33 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:
@@ -295,6 +308,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 +551,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
diff --git a/src/pact/v3/interaction/_http_interaction.py b/src/pact/v3/interaction/_http_interaction.py
index 335d6293cd..1f00bf1946 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:
diff --git a/src/pact/v3/pact.py b/src/pact/v3/pact.py
index 231c01dd79..1c0dd2fd43 100644
--- a/src/pact/v3/pact.py
+++ b/src/pact/v3/pact.py
@@ -64,8 +64,20 @@
 
 import json
 import logging
+import warnings
+from abc import ABC
 from pathlib import Path
-from typing import TYPE_CHECKING, Any, Literal, Set, overload
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Dict,
+    Generator,
+    List,
+    Literal,
+    Set,
+    overload,
+)
 
 from yarl import URL
 
@@ -87,6 +99,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.
@@ -346,52 +472,31 @@ 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,
         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.
@@ -402,13 +507,104 @@ 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
+
+    @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,
@@ -443,32 +639,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.


Test patch:
diff --git a/examples/tests/test_01_provider_fastapi.py b/examples/tests/test_01_provider_fastapi.py
index 7ccd3128aa..a95b5b5f8d 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 ba5c39d432..b7082dabb4 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 0000000000..e69de29bb2
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 0000000000..9016be9b8b
--- /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/tests/v3/compatibility_suite/test_v3_consumer.py b/tests/v3/compatibility_suite/test_v3_consumer.py
index b7014d6f77..f2f218a3fb 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<state>[^"]+)" 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 0000000000..bdb0383686
--- /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<state>[^"]+)" for the message is specified'
+        r"( with the following data:\n)?(?P<table>.*)",
+        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<table>.+)",
+        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<table>.+)",
+        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<basename>[^"]+)" 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<failure>[^"]+)" 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<success>( 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<error>[^"]+)"'))
+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 (?P<success>passed|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<content_type>[^"]+)"'
+    )
+)
+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<state_count>\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<state>[^"]+)"'
+    )
+)
+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<basename>[^"]+)" 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<key>[^"]+)" == "(?P<value>[^"\\]*(?:\\.[^"\\]*)*)"'
+    )
+)
+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<path>[^"]+)" '
+        r'will have been replaced with an? "(?P<expected_type>[^"]+)"'
+    )
+)
+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<state>[^"]+)" for the message '
+        r"will contain the following parameters:\n(?P<table>.+)",
+        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<content_type>[^"]+)"')
+)
+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<key>[^"]+)" == "(?P<value>[^"\\]*(?:\\.[^"\\]*)*)"'
+    )
+)
+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<key>[^"]+)" '
+        r'replaced with an? "(?P<expected_type>[^"]+)"'
+    )
+)
+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<basename>[^"]+)" 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 7b7ab019de..de70ea6f04 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<key>[^"]+)" 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<comment>[^"]+)" 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 "<comment>" 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 0000000000..5b13321651
--- /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<comment>[^"]+)" 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<key>[^"]+)" 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<num>[^ ]+) interaction in the Pact file"
+        r" will have \"(?P<key>[^\"]+)\" = '(?P<value>[^']+)'"
+    ),
+    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<num>[^ ]+) interaction in the Pact file"
+        r' will have a type of "(?P<interaction_type>[^"]+)"'
+    ),
+    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 1351b11462..91b5871d15 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 f9834187df..38bf7676e8 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)
 
diff --git a/tests/v3/test_pact.py b/tests/v3/test_pact.py
index 5f32e7d801..2264346d07 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"