|
| 1 | +--- |
| 2 | +title: V2 Plugin Interface (Draft) |
| 3 | +custom_edit_url: https://github.com/pact-foundation/pact-plugins/edit/main/docs/proposals/001_V2_Plugin_Interface.md |
| 4 | +--- |
| 5 | +<!-- This file has been synced from the pact-foundation/pact-plugins repository. Please do not edit it directly. The URL of the source file can be found in the custom_edit_url value above --> |
| 6 | + |
| 7 | +Discussion for this proposal: https://github.com/pact-foundation/pact-plugins/discussions/83 |
| 8 | + |
| 9 | +## Summary |
| 10 | + |
| 11 | +The current V1 plugin interface has a number of issues, and was released as an MVP to get the plugin architecture |
| 12 | +going. It does not completely implement the plugin design as laid in the design and concept documents. |
| 13 | + |
| 14 | +## Motivation |
| 15 | + |
| 16 | +There are a number of issues with using and/or authoring plugins. We are motivated to address them. |
| 17 | + |
| 18 | +## Details |
| 19 | + |
| 20 | +There are a number of issues with how plugins work, and to date there are really only been two plugins written: |
| 21 | +gRPC/Protobuf and Avro. I think this is a symptom of the complexity of plugins. |
| 22 | + |
| 23 | +Here are some comments taken from a slack discussion: |
| 24 | + |
| 25 | +> Some of the current improvements we’ve identified: ability to mix and match plugins of different types, |
| 26 | +> using plugins at the field or element level (e.g. for JWT headers or keys in payloads), ability for plugin authors |
| 27 | +> to identify matching interactions (see [this issue](https://github.com/pact-foundation/pact-plugins/issues/35)), |
| 28 | +> gRPC vs other approaches debugging plugin issues and more ... |
| 29 | +
|
| 30 | +Issues raised on GitHub: |
| 31 | +- https://github.com/pact-foundation/pact-plugins/issues/35 |
| 32 | +- https://github.com/pact-foundation/pact-plugins/issues/37 |
| 33 | +- https://github.com/pact-foundation/pact-plugins/issues/41 |
| 34 | + |
| 35 | +So I think there are 4 main things to address: |
| 36 | +1. Deal with the logging issues. |
| 37 | +2. Create a V2 plugin interface that completes the original designs and addresses some of the issues in the V1 interface. |
| 38 | +3. Add support for plugins that interact at the field level and provide matching logic. |
| 39 | +4. Allow plugins to be able to use other plugins. |
| 40 | + |
| 41 | +## Technical details |
| 42 | + |
| 43 | +### Deal with the logging issues. |
| 44 | +The original comment was to deal with logging and debugging. Logging issues could be addressed, but debugging will be |
| 45 | +an issue as the plugins run as a separate process and this will not be addressed with this proposal. |
| 46 | + |
| 47 | +Currently, all the plugin standard output is captured and logged via the plugin driver, so it ends up in the standard |
| 48 | +logging used by the Pact framework running the tests. While this means that all the logs end up in one place, at trace |
| 49 | +level, it can be very verbose and not very useful. Also, if the Rust driver is used (calls via FFI use this driver, |
| 50 | +so this is all language implementations except JVM), and the plugin is written in Rust (gRPC is), you get Rust logs |
| 51 | +from both sides and it is hard determine which is which. |
| 52 | + |
| 53 | +The proposal is to not forward plugins output on to the running Pact implementation, but write them to a timestamped |
| 54 | +file in the plugin directory. The gRPC already does this (its logs go both to standard out and a file). |
| 55 | + |
| 56 | +### Create a V2 plugin interface that completes the original designs and addresses some of the issues in the V1 interface |
| 57 | + |
| 58 | +This is split into 2 parts, dealing with the issues in the V1 interface and addressing the missing parts from the |
| 59 | +original design. |
| 60 | + |
| 61 | +#### Issues in the V1 interface |
| 62 | + |
| 63 | +The following issues have been observed in the current plugin interface. These need to be addressed, but will require |
| 64 | +changes to the interface so will have to go into a V2 version. |
| 65 | + |
| 66 | +1. VerifyInteractionRequest passes the Pact through as JSON |
| 67 | +This requires the plugin to be able to parse Pact JSON, so needs to have a Pact implementation as a dependency. It also |
| 68 | +then has to find the correct interaction to verify. The interface should just pass through the relevant data required ( |
| 69 | +the interaction as well as any required metadata). |
| 70 | + |
| 71 | +#### Missing features |
| 72 | + |
| 73 | +The following features from the original design have not been implemented: |
| 74 | + |
| 75 | +##### Allow plugins to match specific data |
| 76 | +This is a mechanism to have plugins add new matchers and generators. The plugin catalog allows defining a MATCHER entry, |
| 77 | +and all the core Pact matchers are exposed in the catalogue, but there is no current way to call out to a plugin to |
| 78 | +do this. This proposal is to add two new RPC calls that a plugin can implement, and then update to the Pact frameworks |
| 79 | +to call out to the plugin when the matcher/generator is required. |
| 80 | + |
| 81 | +```protobuf |
| 82 | +message MatchDataRequest { |
| 83 | + // Catalogue entry for the matcher |
| 84 | + CatalogueEntry entry = 1; |
| 85 | + // Expected data from the Pact |
| 86 | + google.protobuf.Value expectedData = 2; |
| 87 | + // Actual data received |
| 88 | + google.protobuf.Value actualData = 3; |
| 89 | +} |
| 90 | +
|
| 91 | +message MatchDataResult { |
| 92 | + // Any mismatches that occurred |
| 93 | + repeated Mismatch mismatches = 1; |
| 94 | +} |
| 95 | +
|
| 96 | +message Mismatch { |
| 97 | + // Description of the mismatch |
| 98 | + string mismatch = 1; |
| 99 | + // Path to the item that was matched. This is the value as per the documented Pact matching rule expressions. |
| 100 | + string path = 2; |
| 101 | +} |
| 102 | +
|
| 103 | +message GenerateDataRequest { |
| 104 | + // Catalogue entry for the matcher |
| 105 | + CatalogueEntry entry = 1; |
| 106 | + // Expected data from the Pact |
| 107 | + google.protobuf.Value expectedData = 2; |
| 108 | +} |
| 109 | +
|
| 110 | +message GenerateDataResult { |
| 111 | + // Generated data |
| 112 | + google.protobuf.Value generatedData = 1; |
| 113 | +} |
| 114 | +
|
| 115 | +service PactPluginV2 { |
| 116 | + rpc MatchData(MatchDataRequest) returns (MatchDataResult); |
| 117 | + rpc GenerateData(GenerateDataRequest) returns (GenerateDataResult); |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +Issues with this approach: |
| 122 | + |
| 123 | +1. This can only work for data that can be represented as JSON. Binary data is not supported. |
| 124 | + |
| 125 | +##### Capability for plugins to use the functionality from the calling framework |
| 126 | +This will allow plugins to not need to use a Pact framework as a dependency, but use the functionality from the calling |
| 127 | +framework. This will also allow plugins to use functionality provided by other plugins. |
| 128 | + |
| 129 | +The original design had the plugin driver acting like a central hub (see this [sequence diagram](https://github.com/pact-foundation/pact-plugins/blob/main/docs/pact-plugin.png)). |
| 130 | +Each plugin could call back in have things resolved by either another plugin or the core framework. |
| 131 | + |
| 132 | +To implement this, gRPC supports bi-directional streaming connections. So instead of using a unary call, the RPC |
| 133 | +service methods will be updated to allow a sequence of messages to resolve the original request. A high-level example of |
| 134 | +a match JSON body request would go something like (assuming JSON support is from a plugin): |
| 135 | + |
| 136 | +1. Driver sends Start(MatchRequest, ID) message to the plugin with the data and a correlation ID. |
| 137 | +2. Plugin parses the JSON and starts the matching process. |
| 138 | +3. Plugin responds with Continue(MatchDataRequest, ID, ID1) to get a matching rule applied to an item. |
| 139 | +4. Driver calls the Pact framework to resolve the matching rule. |
| 140 | +5. Driver responds with Done(MatchDataResult, ID, ID1) to the plugin. |
| 141 | +6. ... This can continue back and forth until |
| 142 | +7. Plugin responds with Done(MatchDataResponse, ID). |
| 143 | + |
| 144 | +Issues with this approach: |
| 145 | + |
| 146 | +1. This makes the calls asynchronous. If a message is lost for some reason (i.e. driver calls plugin 1, plugin 1 calls |
| 147 | + back for something, driver calls plugin 2, plugin 2 does not respond, the call will never resolve). gRPC |
| 148 | + bi-directional streams allow the client to impose a deadline so that a timeout error is raised if things are not |
| 149 | + resolved in a certain period of time. |
| 150 | +2. This can introduce cyclic dependency issues. The current drivers are only dependent on the models from the core framework, |
| 151 | + but will now need to be dependent on the core framework, while the core framework is also dependent on the driver. |
0 commit comments