Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Lambda DSL variants for request/response #1598

Merged

Conversation

Zabuzard
Copy link
Contributor

@Zabuzard Zabuzard commented Aug 19, 2022

Overview

This request adds Lambda DSL overloads (as typically preferred in Kotlin) to the request/response sections of defining pacts on consumer side.

builder.given(/*...*/)
  .uponReceiving(/*...*/)
  .path(/*...*/) {
    /*...*/ // Lambda DSL
  }.willRespondWith {
    /*...*/ // Lambda DSL
  }.toPact()

This change has been discussed briefly on Slack and was well received: https://pact-foundation.slack.com/archives/C9UN99H24/p1660207829670959

Motivation

Originally, we idiomatically wrote the pact in a single chain, typical for builders:

builder.given("no existing users")
  .uponReceiving("create a new user")
  .path("users")
  .headers("X-Locale", "en-US")
  .method("PUT")
  .willRespondWith()
  .successStatus()
  .body(
    newJsonObject {
      uuid("name")
    }
  ).toPact()

The problem this has is that, in particular if using many matchers, it becomes hard to visually spot where the request or response begin or end.

One solution is to manually add indents to the code (as sometimes seen in the Pact documentation):

builder.given("no existing users")
  .uponReceiving("create a new user")
    .path("users")
    .headers("X-Locale", "en-US")
    .method("PUT")
  .willRespondWith()
    .successStatus()
    .body(
      newJsonObject {
        uuid("name")
      }
  ).toPact()

The problem with this otherwise perfect solution is that adding manual indents is not always an option. There are many environments that enforce code styles that do not allow manual indents.

Lambda DSL solves this problem idiomatically and enhances readability of the pact - if manual indents are not an option for an user.

Minor problem

Ideally, the Lambda DSL for the request would start on uponReceiving. The problem with this is that a request can change its type from PactDslRequestWithoutPath to PactDslRequestWithPath, which makes the DSL hard to get wrong by an unexperienced user. For example, consider:

builder.given("no existing users")
  .uponReceiving("create a new user") {
    path("users") // this call actually creates a new instance, 'WithPath'
    headers("X-Locale", "en-US") // still operates on 'this', which is 'WithoutPath'
    method("PUT")
    // the pact is now missing the path
  }.willRespondWith {
    /*...*/
  }.toPact()

The proper way to write the request in this scenario would be:

builder.given("no existing users")
  .uponReceiving("create a new user") {
    // chain results on the newly 'WithPath' object, not on 'this'
    path("users")
      .headers("X-Locale", "en-US")
      .method("PUT")
  }.willRespondWith {
    /*...*/
  }.toPact()

But this is ugly and misses the point of the DSL that actually wanted to beautify the code.

All in all, there is no satisfying solution to add the DSL on uponReceiving already, the type has to be fix. After the request reached WithPath, there is no way back anymore, so we can safely add the DSL on this stage.

As long as users start with path(/*...*/) before their other matchers, the visual benefit of separating the request from the response is still given. I.e., while valid, it would defeat the purpose if users would write:

builder.given("no existing users")
  .uponReceiving("create a new user")
  .headers("X-Locale", "en-US") // should be inside the DSL, after path
  .path("users") {
      .method("PUT")
  }.willRespondWith {
    /*...*/
  }.toPact()

Samples

The Lambda DSL variants are best explained with a simple code example. The idiomatic way in Kotlin to have code examples in documentation is @sample - that way, the example participates in compilation and does not rot.

It seems that @sample was not used in the code base yet, so I had to come up with a proper place to store them. I decided for a new package next to the code that is explained:
sample package

Feel free to suggest different locations for samples, if you have better ideas 馃檪

* and a sample that showcases them
@rholshausen
Copy link
Contributor

Nice! Very small change that has a huge benefit!

@rholshausen rholshausen merged commit a9e8168 into pact-foundation:master Aug 22, 2022
@rholshausen
Copy link
Contributor

I've merged this, we can continue extending it with the things being discussed on slack

@Zabuzard Zabuzard deleted the lambda_dsl_pact_request_response branch August 23, 2022 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants