pact-foundation / pact-reference

Reference implementations for the pact specifications
https://pact.io
MIT License
91 stars 46 forks source link

Provider States Ignoring Parameter Types #298

Open adamrodger opened 1 year ago

adamrodger commented 1 year ago

If I create an interaction with a provider state parameter that I specify as a string, e.g. in PactNet:

this.pact
    .UponReceiving("a request for an order by ID")
        // 👇👇👇 note that the parameter is a string
        .Given("an order with ID {id} exists", new Dictionary<string, string> { ["id"] = "1" })
        .WithRequest(HttpMethod.Get, "/api/orders/1")
        .WithHeader("Accept", "application/json")
    .WillRespond()
        .WithStatus(HttpStatusCode.OK)
        .WithJsonBody(new { ... });

Then when the interaction is written to the Pact file, the parameter has been converted to a number:

{
  "description": "a request for an order by ID",
  "pending": false,
  "providerStates": [
    {
      "name": "an order with ID {id} exists",
      "params": {
        // 👇👇👇 note that the parameter is now an integer, even though I specified a string
        "id": 1
      }
    }
  ],
  "request": {
    "headers": {
      "Accept": [
        "application/json"
      ]
    },
    "method": "GET",
    "path": "/api/orders/1"
  },
  "response": {
    "body": {
      "content": {
        "date": "2023-06-28T12:13:14.0000000+01:00",
        "id": 1,
        "status": "Pending"
      },
      "contentType": "application/json",
      "encoded": false
    },
    "headers": {
      "Content-Type": [
        "application/json"
      ]
    },
    "status": 200
  },
  "type": "Synchronous/HTTP"
},

This makes writing provider state middleware really tricky, because I now have to guess what the FFI is going to do with converting my types, and I also have to deal with converting everything back to the correct type again, even though I told it the correct type in the first place.

The provider state endpoint will get the following body in a POST request:

{
  "action": "setup",
  // 👇👇👇 note that the parameter is now an integer, even though I specified a string
  "params": { "id": 1 },
  "state":"an order with ID {id} exists"
}

Given that different parameters can have different types and there may be many provider states, that means my only option to deserialise that to Dictionary<string, object> and then deal with all the hassle of having to check what type that object actually is for every provider state handler and then do the conversion myself.

That's really not an ergonomic experience and makes writing provider state handlers really tedious.

Please can it just retain the type that I told it to have instead of trying to be clever and converting the type to something that I don't want?

adamrodger commented 1 year ago

For example, if you try to deserialise the provider state body that's POSTed to DIctionary<string, object>, you get really awkward types in the values. You can't do something like this:

record ProviderStateDto(string State, Dictionary<string, object> Params);

Dictionary<string, object> parameters = JsonConvert.Deserialise<ProviderState>(request.Body);
int id = (int)parameters["id"];

Otherwise you get an invalid cast exception:

image

That's because the deserialiser doesn't have any type information to work with, and thus the parameters dictionary contains a bunch of typed objects:

image

So now you need to do pattern matching or something against the types to work out what's actually in there so you can unwrap it properly. You need to do this with every single provider state parameter you ever use, which is very tedious.

github-actions[bot] commented 5 months ago

🤖 Great news! We've labeled this issue as smartbear-supported and created a tracking ticket in PactFlow's Jira (PACT-1838). We'll keep work public and post updates here. Meanwhile, feel free to check out our docs. Thanks for your patience!