Skip to main content

Plugin drivers

Plugin drivers provide the functionality to find, load and interface to the plugins for the Pact test framework. Each language with a Pact implementation requires a plugin driver to work with plugins.

Main plugin driver responsibilities:

  • The ability to find plugins.
  • Load plugins and extract the plugin manifests that describe what the plugin provides.
  • Provide a catalogue of features provided by the plugins.
  • Provide a messaging bus to facilitate communication between the language implementation and the plugins.
  • Manage the plugin lifecycles.

Conceptual Overview

Conceptual Overview

  1. Your code 😉

  2. The test framework executing tests e.g. Jest, JUnit

  3. The Pact Client library used in the test e.g. Pact JS, Pact JVM

  4. The Shared Core (Rust) or the Java Plugin Driver (for JVM projects) manages the lifecycle of a Pact test and provides key capabilities to client libraries. It oversees the use of plugins via the Plugin Driver

  5. Plugin driver is responsible for discovering, starting and orchestrating plugins.

  6. Plugin may communicate to/from the SUT e.g. in the case of a new transport it will serve as the mock server, and will also issue verification requests at the Provider API

  7. The pact file contains additional information

    • Required plugins
    • Transports
    • Interaction type
    • Markdown to display interactions

Locating plugins

Plugins must be stored on the system in a Pact plugins directory, defined either by the PACT_PLUGIN_DIR environment variable or in the default $HOME/.pact/plugins. Each plugin must be in a separate sub-directory (plugin name/version) and contain a plugin manifest file pact-plugin.json.

Plugin manifest file

The plugin manifest file describes what the plugin provides and how to load it. It is a JSON file with the following attributes:

AttributeDescription
manifestVersionVersion of the manifest file format. Current is 1
pluginInterfaceVersionVersion of the plugin interface the plugin supports. Current is 1
nameName of the plugin
versionVersion of the plugin, following the semver format
executableTypeExecutable type of the plugin. Supported types are: exec (executable binary)
minimumRequiredVersionMinimum required version of the runtime/interpreter to run the plugin
entryPointThe main executable for the plugin
entryPointsOptional map of additional entry points. This allows additional entry points for other operating systems (i.e. requiring a .bat file for Windows)
dependenciesList of system dependencies or plugins required to be able to execute this plugin
argsOptional, comma seperated list of arguments to pass to the plugin entrypoint

Example of a manifest for a plugin written in Ruby that provides matching CSV files:

{
"manifestVersion": 1,
"pluginInterfaceVersion": 1,
"name": "pact-csv",
"version": "0.0.0",
"executableType": "exec",
"minimumRequiredVersion": "2.7.2",
"entryPoint": "bin/bundle",
"args": ["exec", "ruby", "main.rb"]
}

See CSV Plugin and Protobuf plugin for examples of plugin manifests.

Getting the port of the plugin GRPC server

When the plugin is started (except for the DLL executable type), the plugin will print a JSON message to its standard output that contains the port that the plugin GRPC server is running on. The driver needs to poll the plugin standard output for this message.

The message will have the following attributes:

AttributeDescription
portThe port number the GRPC server for the plugin is listening on
serverKeyA randomly generated key required to use as a bearer token when communicating with the plugin

Example:

{"port": 12345, "serverKey": "b37d2d9a9ceb"}

Init request to the plugin

Once the port has been extracted from the plugin standard output, the driver must send a InitPluginRequest message via GRPC to the plugin. The plugin will respond with a InitPluginResponse which will contain all the catalogue entries for the features that the plugin provides. The driver needs to update its catalogue with these entries and then publish the updated catalogue to all loaded plugins (including the new one).

Feature Catalogue

Each entry in the catalogue is keyed based on the following structure: $providerType/$name?/$type/$key, where the different parts are defined by:

AttributeDescription
providerTypeDenotes an entry from the core Pact framework (core) or from a plugin (plugin)
nameThe name of the plugin (omitted for core entries)
typeThe type of the entry. Valid values are: content-matcher, content-generator, matcher, interaction
keyKey for the type. It must be unique withing the entries for the plugin.

For example, a plugin entry for matching CSV bodies would be plugin/csv/content-matcher/csv.

Each entry can also contain associated data in key/value form.

Catalogue entry types

The following describes the types of entries:

Content Matcher (content-matcher)

Content matchers are responsible for matching request and response bodies and message payloads. Each entry must have a content-types associated value with the list of content types the matcher supports (separated by semi-colons ;).

For example, the entry for a CSV content matcher would be:

{
type: EntryType::ContentMatcher,
key: "csv",
values: {
"content-types": "text/csv;application/csv"
}
}

Content Generator (content-generator)

Content matchers are responsible for constructing request and response bodies and message payloads. Each entry must have a content-types associated value with the list of content types the generator supports (separated by semi-colons ;).

For example, the entry for a CSV content generator would be:

{
type: EntryType::ContentGenerator,
key: "csv",
values: {
"content-types": "text/csv;application/csv"
}
}

Matcher (matcher)

Implementation of a matching rule. Mainly used to match the fields and attributes in the bodies and payloads.

Interaction (interaction)

Provides a type of interaction. The standard interaction types are synchronous request/response (HTTP), asynchronous messages (one off or fire and forget) and synchronous messages (request/response, like gRPC).

Core catalogue entries

The driver must provide the following entries from the Pact framework:

KeyDescription
core/interaction/httpSupport Http/1.1 interactions (request/response)
core/interaction/httpsSupport Http/1.1 + TLS interactions (request/response)
core/interaction/messageSupport message interactions
core/matcher/v2-regexV2 spec regex matcher
core/matcher/v2-typeV2 spec type matcher
core/matcher/v3-number-typeV3 spec number matcher
core/matcher/v3-integer-typeV3 spec integer matcher
core/matcher/v3-decimal-typeV3 spec decimal matcher
core/matcher/v3-dateV3 spec date matcher
core/matcher/v3-timeV3 spec time matcher
core/matcher/v3-datetimeV3 spec DateTime matcher
core/matcher/v2-min-typeV2 spec minimum type matcher
core/matcher/v2-max-typeV2 spec maximum type matcher
core/matcher/v2-minmax-typeV2 spec minimum/maximum type matcher
core/matcher/v3-includesV3 spec includes matcher
core/matcher/v3-nullV3 spec null matcher
core/matcher/v4-equals-ignore-orderV4 spec ignore array order matcher matcher
core/matcher/v4-min-equals-ignore-orderV4 spec ignore array order matcher matcher
core/matcher/v4-max-equals-ignore-orderV4 spec ignore array order matcher matcher
core/matcher/v4-minmax-equals-ignore-orderV4 spec ignore array order matcher matcher
core/matcher/v3-content-typeV3 spec content type matcher
core/matcher/v4-array-containsV4 spec array contains matcher
core/matcher/v1-equalityV1 spec equality matcher
core/content-matcher/xmlMatcher for XML content types
core/content-matcher/jsonMatcher for JSON content types
core/content-matcher/textMatcher for Text content types
core/content-matcher/multipart-form-dataMatcher for Multipart Form POST content types
core/content-matcher/form-urlencodedMatcher for URL-encoded Form POST content types
core/content-generator/jsonGenerator for JSON payloads

Plugin driver API

The plugin drivers are required to expose the following API to client language implementations:

Catalogue Manager

The catalogue manager stores all the catalogue entries from the core framework and any loaded plugins.

RegisterPluginEntries(name: String, catalogueList: List<CatalogueEntry<gt;)

Function to add a list of entries for a plugin.

RegisterCoreEntries(entries: List<CatalogueEntry<gt;)

Function to add the list of core framework entries.

Entries

Returns all the catalogue entries

LookupEntry(key: String)

Lookup an entry by key.

FindContentMatcher(contentType: ContentType)

Search the catalogue for a content matcher that supports the given content type.

FindContentGenerator(contentType: ContentType)

Search the catalogue for a content generator that supports the given content type.

RemovePluginEntries(name: String)

Remove all the entries for a plugin. This is needed when a plugin is unloaded.

Plugin Manager

The plugin manager is responsible for finding, loading and unloading plugins. It also provides the interface to call out to the plugin (gRPC stub or channel).

LoadPlugin(plugin: PluginDependency)

Load a plugin given a plugin dependency (name, version and list of dependencies). The plugin manager must keep track of all currently loaded plugins in a global plugin register so that the plugins are not loaded more than once.

Every time a plugin is loaded, send an anonymous event to Google Analytics to track the details of the loaded plugin . To disable tracking, users can set the pact_do_not_track system property or environment variable to true.

The following attributes are sent to GA:

v:      1                   // Version of the API
tid: UA-117778936-1 // Property ID
cid: <UUID> // Anonymous Client ID.
an: <NAME> // App name.
aid: <NAME> // App Id
av: <VERSION> // App version.
aip: true // Anonymise IP address
ds: <PLUGIN NAME> // Data source
cd1: <PLUGIN NAME> // Custom Dimension 1: library
cd2: CI or unknown // Custom Dimension 2: context
cd3: <OS>-<ARCH> // Custom Dimension 3: osarch
cd4: <PLUGIN NAME> // Custom Dimension 4: plugin_name
cd5: <PLUGIN VERSION> // Custom Dimension 5: plugin_version
el: Plugin loaded // Event
ec: Plugin // Category
ea: Loaded // Action
ev: 1 // Value

LookupPlugin(plugin: PluginDependency)

Look up a plugin given a plugin dependency (name, version and list of dependencies) in the global plugin register.

LoadPluginManifest(plugin: PluginDependency)

Return the plugin manifest given a plugin dependency (name, version and list of dependencies). Will first look in the global plugin manifest registry and then load the manifest from disk if not found in the registry.

LookupPluginManifest(plugin: PluginDependency)

Lookup the plugin manifest in the global plugin manifest registry.

InitialisePlugin(manifest: PactPluginManifest)

Start the plugin by calling StartPluginProcess and send the InitPluginRequest message to the plugin. Adds the plugin to the global plugin register and updates the Catalogue Manager with all entries from the plugin.

StartPluginProcess(manifest: PactPluginManifest)

Starts the plugin using the entries from the manifest, then parsers the port and server key from the plugin process standard output.

ShutdownPlugins

Shut down all plugin processes.

PublishUpdatedCatalogue

Every time the catalogue is updated, this function must be called to publish the updated catalogue to all running plugins.