Support Scala collection types in datatable step definitions
gaeljw opened this issue · 6 comments
Describe the solution you'd like
I would like to be be able to write such steps definitions:
Given("the following authors as entries") { (authors: Seq[Author]) =>
// Some code
}Rather than having to use Java types:
Given("the following authors as entries") { (authors: java.util.List[Author]) =>
// Some code
}Same goes for List[List[]] or List[Map[]].
Describe alternatives you've considered
Depending on the faisability, an alternative would be to provide in Cucumer Scala, some implicit methods to convert from DataTable to Scala types. And promote this usage in the documentation.
Additional note
This must not be a breaking change, if people rely on Java Types, their code should obvisouly still continue to work.
Depending on the faisability, an alternative would be to provide in Cucumer Scala, some implicit methods to convert from DataTable to Scala types.
I believe this is feasible. The possible target types for a data table are finite. And as Scala doesn't suffer from type erasure you can create a mapping for al types.
The possible mappings are:
List<List<V>>List<Map<K, V>>Map<K1, Map<K2, V>>Map<K, List<V>>Map<K, V>V
And you can see these including the transpose operation provided by the @Transpose annotation mapped in DataTableTypeRegistryTableConverter.convert:
One problem is that:
JavaType javaType = TypeFactory.constructType(type);
Will only recognize java collection types. I guess it could be made to recognize scala types on a plugin basis but even then it will still output java typed lists and I don't see a good way to change that.
edit: On second though. That might not be as difficult as I thought if the JavaType can provide a way to instantiate the collections.
As an alternative it might be an idea to open up TypeFactory.aListOf and TypeFactory.constructType and add TypeFactory.aMapOf. Then it will be possible to pass a JavaType as the the type to DataTableTypeRegistryTableConverter.covert. The output will still by java collection types but you can then covert these to scala types in your implicit function.
Without having looked at how this is handled in the "core" code, my first idea was to implement it in the ScalaStepDefinition.execute:
- the
ScalaStepDefinition.parameterInfoswould return proper Java types so that the "core" code does everything as it was a usual step definitions with Java types. - the
ScalaStepDefinition.executewould transform the Java types to Scala types
For instance:
Given("the following authors") { (authors: Seq[Author]) =>
// Some code
}Would be seen by Cucumber Core as a StepDefinition with parameterInfos = [ ParameterInfo(type = java.util.List) ] but when applying the execute(args) method, if arg[i] isInstanceOf java.util.List and it was defined as a Scala Seq (we would need to keep the original Scala types in a private attribute), then transform it automatically.
What do you think?
It's just an idea, I haven't actually looked at how easy it would be to implement.
That would be quite neat. If that works, I guess it would depend on the fidelity of the scala type to java type conversion.
I gave it a try, it would work but I think I won't do it because there is a case for which it would mess things up: when a default parameter transformer is used.
For instance the following would not work as expected:
Given("""I have a list {}""") { (list: Seq[Int]) =>
// Whatever
}
DefaultParameterTransformer { (fromValue, toType) =>
// When used with above step definition
// The toType would be a Java type while you'd expect the type defined in the step, that is a Scala type
}I don't know at which point this is a usual or corner case but I think it's better not to do it this way.
For now I think the best thing to do is to incentivize people to use DataTable and provide .asScalaList or .asScalaMap methods on it (through Scala's implicit stuff).
I'll do that and write a bit of documentation about it.
Can't use DataTable: io.cucumber.datatable.UndefinedDataTableTypeException: Can't convert DataTable to Map<java.lang.String, boolean>
I use it like: dataTable.asScalaMap[String, Boolean] in StepDefinitions which extends EN and have imported io.cucumber.scala.Implicits._