Skip to content

Commit 0f4b141

Browse files
committed
Allow creating DataTableType with Option parameters
1 parent f555eef commit 0f4b141

File tree

12 files changed

+251
-17
lines changed

12 files changed

+251
-17
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ See also the [CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/master/CH
1212
### Added
1313

1414
- Add `asScalaRawList[T]`, `asScalaRawMaps[T]` and `asScalaRawLists[T]` on `DataTable` (through `io.cucumber.scala.Implicits`) ([#83](https://github.com/cucumber/cucumber-jvm-scala/issues/83) Gaël Jourdan-Weil)
15+
- Add new `DataTableType` definitions with optional input values ([#84](https://github.com/cucumber/cucumber-jvm-scala/issues/84) Gaël Jourdan-Weil)
16+
- `DataTableType { (entry: Map[String, Option[String]]) => ... }`
17+
- `DataTableType { (row: Seq[Option[String]]) => ... }`
18+
- `DataTableType { (cell: Option[String]) => ... }`
1519

1620
### Changed
1721

docs/transformers.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ This can be achieved in different ways:
8787
- transform cells content to any type
8888

8989
Note that DataTables in Gherkin can not represent `null` or the empty string unambiguously.
90-
Cucumber will interpret empty cells as `null`.
90+
Cucumber will interpret empty cells as `None` or `null`.
9191
But you can use a replacement to represent empty strings.
9292
See below.
9393

@@ -99,8 +99,8 @@ For instance, the following transformer can be defined:
9999
```scala
100100
case class Author(name: String, surname: String, famousBook: String)
101101

102-
DataTableType { entry: Map[String, String] =>
103-
Author(entry("name"), entry("surname"), entry("famousBook"))
102+
DataTableType { entry: Map[String, Option[String]] => // Or Map[String, String]
103+
Author(entry("name").getOrElse("NoValue"), entry("surname").getOrElse("NoValue"), entry("famousBook").getOrElse("NoValue"))
104104
}
105105
```
106106

@@ -131,8 +131,8 @@ For instance, the following transformer can be defined:
131131
```scala
132132
case class Author(name: String, surname: String, famousBook: String)
133133

134-
DataTableType { row: Seq[String] =>
135-
Author(row(0), row(1), row(2))
134+
DataTableType { row: Seq[Option[String]] => // Or Seq[String]
135+
Author(row(0).getOrElse("NoValue"), row(1).getOrElse("NoValue"), row(2).getOrElse("NoValue"))
136136
}
137137
```
138138

@@ -166,8 +166,10 @@ case class Author(name: String, surname: String, famousBook: String)
166166
case class GroupOfAuthor(authors: Seq[Author])
167167

168168
DataTableType { table: DataTable =>
169-
val authors = table.asScalaMaps
170-
.map(entry => Author(entry("name").getOrElse(""), entry("surname").getOrElse(""), entry("famousBook").getOrElse("")))
169+
val authors = table.asMaps().asScala
170+
.map(_.asScala)
171+
.map(entry => Author(entry("name"), entry("surname"), entry("famousBook")))
172+
.toSeq
171173
GroupOfAuthor(authors)
172174
}
173175
```
@@ -195,8 +197,8 @@ For instance, the following transformer can be defined:
195197
```scala
196198
case class RichCell(content: String)
197199

198-
DataTableType { cell: String =>
199-
RichCell(cell)
200+
DataTableType { cell: Option[String] => // Or String
201+
RichCell(cell.getOrElse("NoValue"))
200202
}
201203
```
202204

@@ -243,7 +245,7 @@ Given("the following authors") { (authors: java.util.List[java.util.Map[String,
243245

244246
### Empty values
245247

246-
By default empty values in DataTable are treated as `null` by Cucumber.
248+
By default empty values in DataTable are treated as `None` or `null` by Cucumber.
247249
If you need to have empty values, you can define a replacement like `[empty]` that will be automatically replaced to empty when parsing DataTable.
248250

249251
To do so, you can add a parameter to a `DataTableType` definition.
@@ -252,7 +254,7 @@ For instance, with the following definition:
252254
```scala
253255
case class Author(name: String, surname: String, famousBook: String)
254256

255-
DataTableType("[empty]") { (entry: Map[String, String]) =>
257+
DataTableType("[empty]") { (entry: Map[String, String]) => // Or Map[String, Option[String]]
256258
Author(entry("name"), entry("surname"), entry("famousBook"))
257259
}
258260
```

scala/scala_2.11/src/main/scala/io/cucumber/scala/package.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,25 @@ package object scala {
2121
override def transform(entry: Map[String, String]): T = f.apply(entry)
2222
}
2323

24+
implicit def function1AsDataTableOptionalEntryDefinitionBody[T](f: (Map[String, Option[String]]) => T): DataTableOptionalEntryDefinitionBody[T] = new DataTableOptionalEntryDefinitionBody[T] {
25+
override def transform(entry: Map[String, Option[String]]): T = f.apply(entry)
26+
}
2427

2528
implicit def function1AsDataTableRowDefinitionBody[T](f: (Seq[String]) => T): DataTableRowDefinitionBody[T] = new DataTableRowDefinitionBody[T] {
2629
override def transform(row: Seq[String]): T = f.apply(row)
2730
}
2831

32+
implicit def function1AsDataTableOptionalRowDefinitionBody[T](f: (Seq[Option[String]]) => T): DataTableOptionalRowDefinitionBody[T] = new DataTableOptionalRowDefinitionBody[T] {
33+
override def transform(row: Seq[Option[String]]): T = f.apply(row)
34+
}
2935

3036
implicit def function1AsDataTableCellDefinitionBody[T](f: (String) => T): DataTableCellDefinitionBody[T] = new DataTableCellDefinitionBody[T] {
3137
override def transform(cell: String): T = f.apply(cell)
3238
}
3339

40+
implicit def function1AsDataTableOptionalCellDefinitionBody[T](f: (Option[String]) => T): DataTableOptionalCellDefinitionBody[T] = new DataTableOptionalCellDefinitionBody[T] {
41+
override def transform(cell: Option[String]): T = f.apply(cell)
42+
}
3443

3544
implicit def function1AsDataTableDefinitionBody[T](f: (DataTable) => T): DataTableDefinitionBody[T] = new DataTableDefinitionBody[T] {
3645
override def transform(dataTable: DataTable): T = f.apply(dataTable)

scala/sources/src/main/scala/io/cucumber/scala/DataTableDefinitionBody.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,36 @@ trait DataTableEntryDefinitionBody[T] {
88

99
}
1010

11+
trait DataTableOptionalEntryDefinitionBody[T] {
12+
13+
def transform(entry: Map[String, Option[String]]): T
14+
15+
}
16+
1117
trait DataTableRowDefinitionBody[T] {
1218

1319
def transform(row: Seq[String]): T
1420

1521
}
1622

23+
trait DataTableOptionalRowDefinitionBody[T] {
24+
25+
def transform(row: Seq[Option[String]]): T
26+
27+
}
28+
1729
trait DataTableCellDefinitionBody[T] {
1830

1931
def transform(cell: String): T
2032

2133
}
2234

35+
trait DataTableOptionalCellDefinitionBody[T] {
36+
37+
def transform(cell: Option[String]): T
38+
39+
}
40+
2341
trait DataTableDefinitionBody[T] {
2442

2543
def transform(dataTable: DataTable): T
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.cucumber.scala
2+
3+
import io.cucumber.core.backend.ScenarioScoped
4+
import io.cucumber.datatable.{DataTableType, TableCellTransformer}
5+
6+
trait ScalaDataTableOptionalCellDefinition[T] extends ScalaDataTableTypeDefinition {
7+
8+
val details: ScalaDataTableOptionalCellTypeDetails[T]
9+
10+
override val emptyPatterns: Seq[String] = details.emptyPatterns
11+
12+
override val location: StackTraceElement = new Exception().getStackTrace()(3)
13+
14+
private val transformer: TableCellTransformer[T] = (cell: String) => {
15+
details.body.transform(Option(replaceEmptyPatternsWithEmptyString(cell)))
16+
}
17+
18+
override val dataTableType = new DataTableType(details.tag.runtimeClass, transformer)
19+
20+
}
21+
22+
class ScalaScenarioScopedDataTableOptionalCellDefinition[T](override val details: ScalaDataTableOptionalCellTypeDetails[T]) extends ScalaDataTableOptionalCellDefinition[T] with ScenarioScoped {
23+
}
24+
25+
class ScalaGlobalDataTableOptionalCellDefinition[T](override val details: ScalaDataTableOptionalCellTypeDetails[T]) extends ScalaDataTableOptionalCellDefinition[T] {
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.cucumber.scala
2+
3+
import java.util.{Map => JavaMap}
4+
5+
import io.cucumber.core.backend.ScenarioScoped
6+
import io.cucumber.datatable.{DataTableType, TableEntryTransformer}
7+
8+
import scala.jdk.CollectionConverters._
9+
10+
trait ScalaDataTableOptionalEntryDefinition[T] extends ScalaDataTableTypeDefinition {
11+
12+
val details: ScalaDataTableOptionalEntryTypeDetails[T]
13+
14+
override val emptyPatterns: Seq[String] = details.emptyPatterns
15+
16+
override val location: StackTraceElement = new Exception().getStackTrace()(3)
17+
18+
private val transformer: TableEntryTransformer[T] = (entry: JavaMap[String, String]) => {
19+
replaceEmptyPatternsWithEmptyString(entry.asScala.toMap)
20+
.map(_.map { case (k, v) => (k, Option(v)) })
21+
.map(details.body.transform)
22+
.get
23+
}
24+
25+
override val dataTableType = new DataTableType(details.tag.runtimeClass, transformer)
26+
27+
}
28+
29+
class ScalaScenarioScopedDataTableOptionalEntryDefinition[T](override val details: ScalaDataTableOptionalEntryTypeDetails[T]) extends ScalaDataTableOptionalEntryDefinition[T] with ScenarioScoped {
30+
}
31+
32+
class ScalaGlobalDataTableOptionalEntryDefinition[T](override val details: ScalaDataTableOptionalEntryTypeDetails[T]) extends ScalaDataTableOptionalEntryDefinition[T] {
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.cucumber.scala
2+
3+
import java.util.{List => JavaList}
4+
5+
import io.cucumber.core.backend.ScenarioScoped
6+
import io.cucumber.datatable.{DataTableType, TableRowTransformer}
7+
8+
import scala.jdk.CollectionConverters._
9+
10+
trait ScalaDataTableOptionalRowDefinition[T] extends ScalaDataTableTypeDefinition {
11+
12+
val details: ScalaDataTableOptionalRowTypeDetails[T]
13+
14+
override val emptyPatterns: Seq[String] = details.emptyPatterns
15+
16+
override val location: StackTraceElement = new Exception().getStackTrace()(3)
17+
18+
private val transformer: TableRowTransformer[T] = (row: JavaList[String]) => {
19+
details.body.transform(row.asScala.map(replaceEmptyPatternsWithEmptyString).map(Option.apply).toSeq)
20+
}
21+
22+
override val dataTableType = new DataTableType(details.tag.runtimeClass, transformer)
23+
24+
}
25+
26+
class ScalaScenarioScopedDataTableOptionalRowDefinition[T](override val details: ScalaDataTableOptionalRowTypeDetails[T]) extends ScalaDataTableOptionalRowDefinition[T] with ScenarioScoped {
27+
}
28+
29+
class ScalaGlobalDataTableOptionalRowDefinition[T](override val details: ScalaDataTableOptionalRowTypeDetails[T]) extends ScalaDataTableOptionalRowDefinition[T] {
30+
}

scala/sources/src/main/scala/io/cucumber/scala/ScalaDataTableTypeDefinition.scala

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,35 @@ object ScalaDataTableTypeDefinition {
1818
} else {
1919
new ScalaGlobalDataTableEntryDefinition[T](entryDetails)
2020
}
21+
case entryDetails@ScalaDataTableOptionalEntryTypeDetails(_, _, _) =>
22+
if (scenarioScoped) {
23+
new ScalaScenarioScopedDataTableOptionalEntryDefinition[T](entryDetails)
24+
} else {
25+
new ScalaGlobalDataTableOptionalEntryDefinition[T](entryDetails)
26+
}
2127
case rowDetails@ScalaDataTableRowTypeDetails(_, _, _) =>
2228
if (scenarioScoped) {
2329
new ScalaScenarioScopedDataTableRowDefinition[T](rowDetails)
2430
} else {
2531
new ScalaGlobalDataTableRowDefinition[T](rowDetails)
2632
}
27-
case rowDetails@ScalaDataTableCellTypeDetails(_, _, _) =>
33+
case rowDetails@ScalaDataTableOptionalRowTypeDetails(_, _, _) =>
34+
if (scenarioScoped) {
35+
new ScalaScenarioScopedDataTableOptionalRowDefinition[T](rowDetails)
36+
} else {
37+
new ScalaGlobalDataTableOptionalRowDefinition[T](rowDetails)
38+
}
39+
case cellDetails@ScalaDataTableCellTypeDetails(_, _, _) =>
40+
if (scenarioScoped) {
41+
new ScalaScenarioScopedDataTableCellDefinition[T](cellDetails)
42+
} else {
43+
new ScalaGlobalDataTableCellDefinition[T](cellDetails)
44+
}
45+
case cellDetails@ScalaDataTableOptionalCellTypeDetails(_, _, _) =>
2846
if (scenarioScoped) {
29-
new ScalaScenarioScopedDataTableCellDefinition[T](rowDetails)
47+
new ScalaScenarioScopedDataTableOptionalCellDefinition[T](cellDetails)
3048
} else {
31-
new ScalaGlobalDataTableCellDefinition[T](rowDetails)
49+
new ScalaGlobalDataTableOptionalCellDefinition[T](cellDetails)
3250
}
3351
case rowDetails@ScalaDataTableTableTypeDetails(_, _, _) =>
3452
if (scenarioScoped) {

scala/sources/src/main/scala/io/cucumber/scala/ScalaDataTableTypeDetails.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@ sealed trait ScalaDataTableTypeDetails[T]
66

77
case class ScalaDataTableEntryTypeDetails[T](emptyPatterns: Seq[String], body: DataTableEntryDefinitionBody[T], tag: ClassTag[T]) extends ScalaDataTableTypeDetails[T]
88

9+
case class ScalaDataTableOptionalEntryTypeDetails[T](emptyPatterns: Seq[String], body: DataTableOptionalEntryDefinitionBody[T], tag: ClassTag[T]) extends ScalaDataTableTypeDetails[T]
10+
911
case class ScalaDataTableRowTypeDetails[T](emptyPatterns: Seq[String], body: DataTableRowDefinitionBody[T], tag: ClassTag[T]) extends ScalaDataTableTypeDetails[T]
1012

13+
case class ScalaDataTableOptionalRowTypeDetails[T](emptyPatterns: Seq[String], body: DataTableOptionalRowDefinitionBody[T], tag: ClassTag[T]) extends ScalaDataTableTypeDetails[T]
14+
1115
case class ScalaDataTableCellTypeDetails[T](emptyPatterns: Seq[String], body: DataTableCellDefinitionBody[T], tag: ClassTag[T]) extends ScalaDataTableTypeDetails[T]
1216

17+
case class ScalaDataTableOptionalCellTypeDetails[T](emptyPatterns: Seq[String], body: DataTableOptionalCellDefinitionBody[T], tag: ClassTag[T]) extends ScalaDataTableTypeDetails[T]
18+
1319
case class ScalaDataTableTableTypeDetails[T](emptyPatterns: Seq[String], body: DataTableDefinitionBody[T], tag: ClassTag[T]) extends ScalaDataTableTypeDetails[T]
1420

scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,26 @@ private[scala] trait DataTableTypeDsl extends BaseScalaDsl {
208208
registry.registerDataTableType(ScalaDataTableEntryTypeDetails[T](replaceWithEmptyString, body, ev))
209209
}
210210

211+
def apply[T](body: DataTableOptionalEntryDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
212+
registry.registerDataTableType(ScalaDataTableOptionalEntryTypeDetails[T](replaceWithEmptyString, body, ev))
213+
}
214+
211215
def apply[T](body: DataTableRowDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
212216
registry.registerDataTableType(ScalaDataTableRowTypeDetails[T](replaceWithEmptyString, body, ev))
213217
}
214218

219+
def apply[T](body: DataTableOptionalRowDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
220+
registry.registerDataTableType(ScalaDataTableOptionalRowTypeDetails[T](replaceWithEmptyString, body, ev))
221+
}
222+
215223
def apply[T](body: DataTableCellDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
216224
registry.registerDataTableType(ScalaDataTableCellTypeDetails[T](replaceWithEmptyString, body, ev))
217225
}
218226

227+
def apply[T](body: DataTableOptionalCellDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
228+
registry.registerDataTableType(ScalaDataTableOptionalCellTypeDetails[T](replaceWithEmptyString, body, ev))
229+
}
230+
219231
def apply[T](body: DataTableDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
220232
registry.registerDataTableType(ScalaDataTableTableTypeDetails[T](replaceWithEmptyString, body, ev))
221233
}

0 commit comments

Comments
 (0)