Skip to content

Add Implicits object to help datatable transformations #61

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

Merged
merged 1 commit into from
May 22, 2020
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ See also the [CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/master/CH

### Added

- [Scala] Conversion methods from `DataTable` to scala types ([#56](https://github.com/cucumber/cucumber-jvm-scala/issues/56) Gaël Jourdan-Weil)

### Changed

### Deprecated
Expand Down
90 changes: 77 additions & 13 deletions docs/datatables.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# DataTables

Cucumber Scala support DataTables with Java types.
Cucumber Scala support DataTables with either:
- Scala types using a `DataTable` as step definition argument and implicit conversions by importing `import io.cucumber.scala.Implicits._`
- Java types in the step definitions arguments

**The benefit of using Scala types** if that you will be handling `Option`s instead of potentially `null` values in the Java collections.

See below the exhaustive list of possible mappings.

Expand All @@ -10,15 +14,25 @@ See below the exhaustive list of possible mappings.
Given the following table as Map of Map
| | key1 | key2 | key3 |
| row1 | val11 | val12 | val13 |
| row2 | val21 | val22 | val23 |
| row2 | val21 | | val23 |
| row3 | val31 | val32 | val33 |
```

```scala
Given("the following table as Map of Map") { (table: DataTable) =>
val scalaTable: Map[String, Map[String, Option[String]]] = table.asScalaRowColumnMap
// Map(
// "row1" -> Map("key1" -> Some("val11"), "key2" -> Some("val12"), "key3" -> Some("val13")),
// "row2" -> Map("key1" -> Some("val21"), "key2" -> None, "key3" -> Some("val23")),
// "row3" -> Map("key1" -> Some("val31"), "key2" -> Some("val32"), "key3" -> Some("val33"))
// )
}

// Or:
Given("the following table as Map of Map") { (table: JavaMap[String, JavaMap[String, String]]) =>
// Map(
// "row1" -> Map("key1" -> "val11", "key2" -> "val12", "key3" -> "val13"),
// "row2" -> Map("key1" -> "val21", "key2" -> "val22", "key3" -> "val23"),
// "row2" -> Map("key1" -> "val21", "key2" -> null, "key3" -> "val23"),
// "row3" -> Map("key1" -> "val31", "key2" -> "val32", "key3" -> "val33")
// )
}
Expand All @@ -30,15 +44,25 @@ Given("the following table as Map of Map") { (table: JavaMap[String, JavaMap[Str
Given the following table as List of Map
| key1 | key2 | key3 |
| val11 | val12 | val13 |
| val21 | val22 | val23 |
| val21 | | val23 |
| val31 | val32 | val33 |
```

```scala
Given("the following table as List of Map") { (table: DataTable) =>
val scalaTable: Seq[Map[String, Option[String]]] = table.asScalaMaps
// Seq(
// Map("key1" -> Some("val11"), "key2" -> Some("val12"), "key3" -> Some("val13")),
// Map("key1" -> Some("val21"), "key2" -> None, "key3" -> Some("val23")),
// Map("key1" -> Some("val31"), "key2" -> Some("val32"), "key3" -> Some("val33"))
// )
}

// Or:
Given("the following table as List of Map") { (table: JavaList[JavaMap[String, String]]) =>
// Seq(
// Map("key1" -> "val11", "key2" -> "val12", "key3" -> "val13"),
// Map("key1" -> "val21", "key2" -> "val22", "key3" -> "val23"),
// Map("key1" -> "val21", "key2" -> null, "key3" -> "val23"),
// Map("key1" -> "val31", "key2" -> "val32", "key3" -> "val33")
// )
}
Expand All @@ -49,15 +73,25 @@ Given("the following table as List of Map") { (table: JavaList[JavaMap[String, S
```gherkin
Given the following table as Map of List
| row1 | val11 | val12 | val13 |
| row2 | val21 | val22 | val23 |
| row2 | val21 | | val23 |
| row3 | val31 | val32 | val33 |
```

```scala
Given("the following table as Map of List") { (table: DataTable) =>
val scalaTable: Map[Seq[Option[String]]] = table.asScalaRowMap
// Map(
// "row1" -> Seq(Some("val11"), Some("val12"), Some("val13")),
// "row2" -> Seq(Some("val21"), None, Some("val23")),
// "row3" -> Seq(Some("val31"), Some("val32"), Some("val33"))
// )
}

// Or:
Given("the following table as Map of List") { (table: JavaMap[String, JavaList[String]]) =>
// Map(
// "row1" -> Seq("val11", "val12", "val13"),
// "row2" -> Seq("val21", "val22", "val23"),
// "row2" -> Seq("val21", null, "val23"),
// "row3" -> Seq("val31", "val32", "val33")
// )
}
Expand All @@ -69,15 +103,25 @@ Given("the following table as Map of List") { (table: JavaMap[String, JavaList[S
```gherkin
Given the following table as List of List
| val11 | val12 | val13 |
| val21 | val22 | val23 |
| val21 | | val23 |
| val31 | val32 | val33 |
```

```scala
Given("the following table as List of List") { (table: DataTable) =>
val scalaTable: Seq[Seq[Option[String]]] = table.asScalaLists
// Seq(
// Seq(Some("val11"), Some("val12"), Some("val13")),
// Seq(Some("val21"), None, Some("val23")),
// Seq(Some("val31"), Some("val32"), Some("val33"))
// )
}

// Or:
Given("the following table as List of List") { (table: JavaList[JavaList[String]]) =>
// Seq(
// Seq("val11", "val12", "val13"),
// Seq("val21", "val22", "val23"),
// Seq("val21", null, "val23"),
// Seq("val31", "val32", "val33")
// )
}
Expand All @@ -88,15 +132,25 @@ Given("the following table as List of List") { (table: JavaList[JavaList[String]
```gherkin
Given the following table as Map
| row1 | val11 |
| row2 | val21 |
| row2 | |
| row3 | val31 |
```

```scala
Given("the following table as Map") { (table: DataTable) =>
val scalaTable: Map[String, Option[String]] = table.asScalaMap[String, String]
// Map(
// "row1" -> Some("val11"),
// "row2" -> None,
// "row3" -> Some("val31")
// )
}

// Or:
Given("the following table as Map") { (table: JavaMap[String, String]) =>
// Map(
// "row1" -> "val11",
// "row2" -> "val21",
// "row2" -> null,
// "row3" -> "val31"
// )
}
Expand All @@ -107,15 +161,25 @@ Given("the following table as Map") { (table: JavaMap[String, String]) =>
```gherkin
Given the following table as List
| val11 |
| val21 |
| |
| val31 |
```

```scala
Given("the following table as List") { (table: DataTable) =>
val scalaTable: Seq[Option[String]] = table.asScalaList
// Seq(
// Some("val11"),
// None,
// Some("val31")
// )
}

// Or:
Given("the following table as List") { (table: JavaList[String]) =>
// Seq(
// "val11",
// "val21",
// null,
// "val31"
// )
}
Expand Down
155 changes: 155 additions & 0 deletions scala/sources/src/main/scala/io/cucumber/scala/Implicits.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package io.cucumber.scala

import io.cucumber.datatable.DataTable

import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag

/**
* Contains implicit helpers for Cucumber Scala users.
*/
object Implicits {

/**
* DataTable extension class providing methods to read a DataTable as Scala types.
* <p>
* <em>Note: we do not filter out null values because users might rely on the keyset in their implementation.</em>
*/
implicit class ScalaDataTable(table: DataTable) {

def asScalaDataTable: ScalaDataTable = this

/**
* Provides a view of the DataTable as a sequence of rows, each row being a key-value map where key is the column name.
* Equivalent of `.asMaps[K,V](classOf[K], classOf[V])` but returned as Scala collection types without `null` values.
*
* @tparam K key type
* @tparam V value type
* @return sequence of rows
*/
def asScalaMaps[K, V](implicit evK: ClassTag[K], evV: ClassTag[V]): Seq[Map[K, Option[V]]] = {
table.asMaps[K, V](evK.runtimeClass, evV.runtimeClass)
.asScala
.map(_.asScala.map(nullToNone).toMap)
.toSeq
}

/**
* Provides a view of the DataTable as a sequence of rows, each row being a key-value map where key is the column name.
* Equivalent of `.asMaps()` but returned as Scala collection types without `null` values.
*
* @return sequence of rows
*/
def asScalaMaps: Seq[Map[String, Option[String]]] = asScalaMaps[String, String]

/**
* Provides a view of the DataTable as a key-value map where key are the first column values.
* Equivalent of `.asMap[K,V](classOf[K],classOf[V])` but returned as Scala collection types without `null` values.
*
* @tparam K key type
* @tparam V value type
* @return key-value map
*/
def asScalaMap[K, V](implicit evK: ClassTag[K], evV: ClassTag[V]): Map[K, Option[V]] = {
table.asMap[K, V](evK.runtimeClass, evV.runtimeClass)
.asScala
.map(nullToNone)
.toMap
}

/**
* Provides a view of the DataTable as a matrix.
* Equivalent of `.asLists[T](classOf[T])` but returned as Scala collection types without `null` values.
*
* @tparam T cell type
* @return matrix
*/
def asScalaLists[T](implicit ev: ClassTag[T]): Seq[Seq[Option[T]]] = {
table.asLists[T](ev.runtimeClass)
.asScala
.map(_.asScala.map(Option.apply).toSeq)
.toSeq
}

/**
* Provides a view of the DataTable as a matrix.
* Equivalent of `.asLists()` but returned as Scala collection types without `null` values.
*
* @return matrix
*/
def asScalaLists: Seq[Seq[Option[String]]] = asScalaLists[String]

/**
* Provides a view of the DataTable as a simple list of values.
* Equivalent of `.asList[T](classOf[T])` but returned as Scala collection types without `null` values.
*
* @tparam T cell type
* @return list of values
*/
def asScalaList[T](implicit ev: ClassTag[T]): Seq[Option[T]] = {
table.asList[T](ev.runtimeClass)
.asScala
.map(Option.apply)
.toSeq
}

/**
* Provides a view of the DataTable as a simple list of values.
* Equivalent of `.asList()` but returned as Scala collection types without `null` values.
*
* @return list of values
*/
def asScalaList: Seq[Option[String]] = asScalaList[String]

/**
* Provides a view of the DataTable as a full table: a key-value map of row where keys are the first column values
* and each row being itself a key-value map where key is the column name.
*
* @tparam K key type
* @return map of rows
*/
def asScalaRowColumnMap[K](implicit evK: ClassTag[K]): Map[K, Map[String, Option[String]]] = {
table.asMap[K, java.util.Map[String, String]](evK.runtimeClass, classOf[java.util.Map[String, String]])
.asScala
.map { case (k, v) => (k, v.asScala.map(nullToNone).toMap) }
.toMap
}

/**
* Provides a view of the DataTable as a full table: a key-value map of row where keys are the first column values
* and each row being itself a key-value map where key is the column name.
*
* @return map of rows
*/
def asScalaRowColumnMap: Map[String, Map[String, Option[String]]] = asScalaRowColumnMap[String]

/**
* Provides a view of the DataTable as a key-value map of row where keys are the first column values
* and each row being a list of values.
*
* @tparam K key type
* @return map of rows
*/
def asScalaRowMap[K](implicit evK: ClassTag[K]): Map[K, Seq[Option[String]]] = {
table.asMap[K, java.util.List[String]](evK.runtimeClass, classOf[java.util.List[String]])
.asScala
.map { case (k, v) => (k, v.asScala.map(Option.apply).toSeq) }
.toMap
}

/**
* Provides a view of the DataTable as a key-value map of row where keys are the first column values
* and each row being a list of values.
*
* @return map of rows
*/
def asScalaRowMap: Map[String, Seq[Option[String]]] = asScalaRowMap[String]

private def nullToNone[K, V](tuple: (K, V)): (K, Option[V]) = {
val (k, v) = tuple
(k, Option(v))
}

}

}
Loading