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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🚀 Add a ScalaDsl #355

Merged
merged 1 commit into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,61 @@ The modules `pact4s-munit-cats-effect`, `pact4s-weaver` and `pact4s-scalatest` m

Pacts are constructed using the pact-jvm DSL, but with additional helpers for easier interoperability with scala. For example, anywhere a java `Map` is expected, a scala `Map`, or scala tuples can be provided instead.

#### Using Pact matching DSL

Using `PactDslJsonBody` or `PactDslJsonArray` can be painful.
This is why Pact JVM exposes a `LambdaDsl` which aims to be easier to use and read (Read ["Why a new DSL implementation?"](https://docs.pact.io/implementation_guides/jvm/consumer#why-a-new-dsl-implementation)).

But still, in Scala it can be very verbose to use it.
Pact4s provides a `ScalaDsl` trait to reduce verbosity.

For instance, see below how the following JSON body can be expressed with both DSLs:

```json
{
"keyA": {
"a1": "...",
"a2": "..."
},
"keyB": [1]
}
```

```scala
// Pact JVM LambdaDsl
val dsl: DslPart = LambdaDsl
.newJsonBody { rootObj =>
rootObj.`object`(
"keyA",
o => {
o.stringType("a1")
o.stringType("a2")
()
}
)
rootObj.array(
"keyB",
a => {
a.integerType()
()
}
)
()
}
.build()

// Pact4s ScalaDsl
val dsl: DslPart = newJsonObject { rootObj =>
rootObj.newObject("keyA") { o =>
o.stringType("a1")
o.stringType("a2")
}
rootObj.newArray("keyB") { a =>
a.integerType()
}
}
```

#### Using JSON bodies

If you want to construct simple pacts with bodies that do not use the pact-jvm matching dsl, (`PactDslJsonBody`), a scala data type `A` can be passed to `.body` directly, provided there is an implicit instance of `pact4s.PactBodyEncoder[A]` provided.
Expand Down
86 changes: 86 additions & 0 deletions shared/src/main/scala/pact4s/dsl/ScalaDsl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2021 io.github.jbwheatley
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package pact4s.dsl

import au.com.dius.pact.consumer.dsl.{DslPart, LambdaDsl, LambdaDslJsonArray, LambdaDslObject}

trait ScalaDsl {

// Note: the body parameters below are typed as X => Any instead of of X => Unit
// It's meant as a convenience for users so that they don't get the "discarded value" warning/error

def newJsonObject(body: LambdaDslObject => Any): DslPart =
LambdaDsl
.newJsonBody { obj =>
body.apply(obj)
()
}
.build()

def newJsonArray(body: LambdaDslJsonArray => Any): DslPart =
LambdaDsl
.newJsonArray { array =>
body.apply(array)
()
}
.build()

implicit def toLambdaDslObjectOps(obj: LambdaDslObject): LambdaDslObjectOps = new LambdaDslObjectOps(obj)

implicit def toLambdaDslJsonArrayOps(array: LambdaDslJsonArray): LambdaDslJsonArrayOps = new LambdaDslJsonArrayOps(
array
)

}

class LambdaDslObjectOps(val obj: LambdaDslObject) extends AnyVal {

def newArray(name: String)(body: LambdaDslJsonArray => Any): LambdaDslObject =
obj.array(
name,
{ a =>
body.apply(a)
()
}
)

def newObject(name: String)(body: LambdaDslObject => Any): LambdaDslObject =
obj.`object`(
name,
{ o =>
body.apply(o)
()
}
)

}

class LambdaDslJsonArrayOps(val array: LambdaDslJsonArray) extends AnyVal {

def newArray(body: LambdaDslJsonArray => Any): LambdaDslJsonArray =
array.array { a =>
body.apply(a)
()
}

def newObject(body: LambdaDslObject => Any): LambdaDslJsonArray =
array.`object` { o =>
body.apply(o)
()
}

}
108 changes: 108 additions & 0 deletions shared/src/test/scala/pact4s/dsl/ScalaDslTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package pact4s.dsl

import au.com.dius.pact.consumer.dsl.{DslPart, LambdaDsl}
import munit.FunSuite

class ScalaDslTest extends FunSuite {

test("Array extension methods are consistent") {

object TraditionalDsl {

val dsl: DslPart = LambdaDsl
.newJsonArray { rootArray =>
rootArray.array { a =>
a.stringValue("a1")
a.stringValue("a2")
()
}
rootArray.array { a =>
a.numberValue(1).numberValue(2)
()
}
rootArray.array { a =>
a.`object` { o =>
o.stringValue("foo", "Foo")
()
}
()
}
()
}
.build()

}

object NewDsl extends ScalaDsl {

val dsl: DslPart = newJsonArray { rootArray =>
rootArray.newArray { a =>
a.stringValue("a1")
a.stringValue("a2")
}
rootArray.newArray { a =>
a.numberValue(1)
a.numberValue(2)
}
rootArray.newArray { a =>
a.newObject { o =>
o.stringValue("foo", "Foo")
}
}
}

}

assertDslEquals(NewDsl.dsl, TraditionalDsl.dsl)
}

test("Object extension methods are consistent") {

object TraditionalDsl {

val dsl: DslPart = LambdaDsl
.newJsonBody { rootObj =>
rootObj.`object`(
"keyA",
o => {
o.stringType("a1")
o.stringType("a2")
()
}
)
rootObj.array(
"keyB",
a => {
a.integerType()
()
}
)
()
}
.build()

}

object NewDsl extends ScalaDsl {

val dsl: DslPart = newJsonObject { rootObj =>
rootObj.newObject("keyA") { o =>
o.stringType("a1")
o.stringType("a2")
}
rootObj.newArray("keyB") { a =>
a.integerType()
}
}

}

assertDslEquals(NewDsl.dsl, TraditionalDsl.dsl)

}

private def assertDslEquals(actualDsl: DslPart, expectedDsl: DslPart): Unit =
// Comparing bodies gives a good level of confidence that both DSL are equivalent
assertEquals(actualDsl.getBody, expectedDsl.getBody)

}