An example plugin providing a field-level matching rule and generator for credit card numbers. Unlike the CSV, Protobuf and JWT plugins, it does not own a content type at all - it contributes one rule that applies to a single value inside somebody else's content: a field in a JSON body, a header, a message metadata value, and so on.
It is the reference implementation for proposal 006, Field-level matchers and generators, and was written alongside that design to keep it honest. Like the JWT plugin, it is written in Lua and runs embedded in the driver's own process rather than as a separate gRPC server - see Writing plugins in Lua.
See examples/creditcard for consumer tests in Rust and Java, and a provider that
both verify against. test.lua also exercises the plugin standalone, with no driver involved.
The plugin registers two catalogue entries, both named creditcard:
| Entry type | What it does |
|---|---|
MATCHER |
Asserts the actual value is a plausible credit card number |
GENERATOR |
Produces a fresh, valid credit card number on each test run |
The matcher checks, in order:
- The value is text (or an integer) - binary data and other types are a mismatch.
- After removing spaces and dashes, it is all digits.
- Its last digit is a valid Luhn check digit. This is the part no
core Pact matching rule can express:
regexcan check the shape of a card number, but not its checksum. - If the rule is configured with a
brand, the number starts with an issuer identification number used by that brand and has a length that brand issues. If nobrandis configured, any well-formed number of 12 to 19 digits is accepted, whichever issuer it belongs to.
Known brands: amex, diners, discover, jcb, mastercard, visa. Configuring a brand the plugin doesn't know
is reported as an error rather than a mismatch - it's a mistake in the test, not in the provider's response.
The generator produces a number for the configured brand; with no brand configured it uses whichever brand the
example value in the Pact file looks like, falling back to Visa. It is a pure function of the request - the brand,
the example value, and the test context are all it reads.
Set the rule up on a field the same way as any core matching rule, by name:
{
"card": {
"number": "matching(creditcard, 'visa', '4111111111111111')",
"expiry": "matching(regex, '\\d{2}/\\d{2}', '04/28')"
}
}The optional middle argument is the brand - the plugin declares config-key = "brand" on its catalogue entries,
which is what maps that positional argument onto the rule's brand value. The equivalent JSON form, which can carry
any number of configuration values and can attach the generator too, is:
{
"pact:matcher:type": "creditcard",
"pact:generator:type": "creditcard",
"brand": "visa",
"value": "4111111111111111"
}Either way it is persisted into the Pact file as an ordinary matching rule whose name happens to come from a plugin:
"matchingRules": {
"body": {
"$.card.number": {
"combine": "AND",
"matchers": [ { "match": "creditcard", "brand": "visa" } ]
}
}
}The consumer test has to load the plugin (usingPlugin("creditcard") or your framework's equivalent) before the
rule can be resolved.
plugin.lua- the entry point (seeentryPointinpact-plugin.json). Definesinit,match_fieldandgenerate_field.creditcard.lua- the card number logic itself: normalisation, the Luhn checksum, per-brand IIN prefixes and lengths, brand detection and number generation. Knows nothing about Pact.test.lua- a standalone test harness that stands in for the driver.
There are no vendored third-party libraries - everything the plugin needs is in Lua's standard library.
$ lua5.4 test.lua
ok - init returns two catalogue entries
...
All checks passedThe harness stubs the logger host function, loads plugin.lua, and calls its global functions with the same table
shapes the driver builds. It covers each scheme's published test numbers, the mismatch cases, and round-trips every
generated number back through the matcher.
There's no build step - it's plain Lua files. Run install-local.sh, which copies the plugin
into $PACT_PLUGIN_DIR/creditcard-0.0.0/ (or ~/.pact/plugins/creditcard-0.0.0/ if PACT_PLUGIN_DIR isn't set):
$ ./install-local.sh
Installed the creditcard plugin into /home/you/.pact/plugins/creditcard-0.0.0Requires a driver that implements field-level matchers and generators (proposal 006), built with Lua plugin support enabled, and a host Pact framework that can carry a plugin-provided matching rule:
pact-plugin-driver1.2.0 /io.pact.plugin.driver:core1.2.0 or later, for theGENERATORcatalogue entry type, theMatchField/GenerateFieldoperations and the Luamatch_field/generate_fieldentry points this plugin defines;- on the Rust side,
pact_models/pact_matching/pact_consumerwith thePlugincarrier variants; on the JVM side,core:modelandcore:matcherswithPluginMatcher/PluginGenerator.
pact_verifier_cli needs the driver's lua feature turned on to load this plugin at all - see the
JWT example README for how to build one
from a local checkout.