Skip to content

Commit

Permalink
updated dependencies to scala 2.12 and cats 0.9
Browse files Browse the repository at this point in the history
  • Loading branch information
kailuowang committed Mar 21, 2017
1 parent 0ece8d0 commit e8f0eb2
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 63 deletions.
78 changes: 43 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,34 +30,43 @@ limitations under the License.

## Modules

1. `henkan.convert` - transform between case classes, which minimize the need to manually using constructor to transform information from one case class to another.
### `henkan.convert`

Transform between case classes, which minimize the need to manually using constructor to transform information from one case class to another.

*Features*:

a. quick transformation when the source case class has all the fields the target case class has: e.g. `a.to[B]()`
1. quick transformation when the source case class has all the fields the target case class has: e.g. `a.to[B]()`

2. supplement (if source case class doesn't have a field) or override field values. e.g. `a.to[B].set(foo = "bar")`

b. supplement (if source case class doesn't have a field) or override field values. e.g. `a.to[B].set(foo = "bar")`
3. use the default values of the target case classes if needed

c. use the default values of the target case classes if needed
### `henkan.extract`

2. `henkan.extract` - transform between a runtime data type and a case class. Usually this type of transformation is done either manually or through some macro generated transformers. Using shapeless can achieve this as well, henkan is providing a generic transformer library on top of shapeless, which minimizes the boilerplate needed. However this part is also experimental and, as of now, limited than the macro solution.
Transform between a runtime data type and a case class. Usually this type of transformation is done either manually or through some macro generated transformers. Using shapeless can achieve this as well, henkan is providing a generic transformer library on top of shapeless, which minimizes the boilerplate needed. However this part is also experimental and, as of now, limited than the macro solution.

*Features*:

a. transform any runtime data type to an arbitrary Monad of taget case class - you just need to provide some `FieldReader`s that can read primitive values out of the runtime data type given a field name.
1. transform any runtime data type to an arbitrary Monad of taget case class - you just need to provide some `FieldReader`s that can read primitive values out of the runtime data type given a field name.

b. supports default value.
2. supports default value.

c. support recursive case classes, i.e. case class that has case class fields.
3. support recursive case classes, i.e. case class that has case class fields.

### Known issues for this feature
*Known issues for this feature*

* [Error when the last field is a nested class](https://github.com/kailuowang/henkan/issues/15)


3. `henkan.k` building blocks for generic function compositions.
### `henkan.k`

Building blocks for generic function compositions.

### `henkan.optional`

Conversion between case classes with optional fields and case class with required fields. One of the use cases for such conversions is conversion between scalaPB generated classes where most fields are Options and internal case classes where you have required fields.

4. `henkan.optional` conversion between case classes with optional fields and case class with required fields. One of the use cases for such conversions is conversion between scalaPB generated classes where most fields are Options and internal case classes where you have required fields.


## Get started
Expand Down Expand Up @@ -93,29 +102,29 @@ val unionMember = UnionMember("Micheal", "41 Dunwoody St", LocalDate.of(1994, 7,

Now use the henkan magic to transform between `UnionMember` and `Employee`
```scala
scala> import henkan.convert.Syntax._
import henkan.convert.Syntax._
// import henkan.convert.Syntax._

scala> employee.to[UnionMember]()
res4: UnionMember = UnionMember(George,123 E 86 St,1963-03-12)
employee.to[UnionMember]()
// res4: UnionMember = UnionMember(George,123 E 86 St,1963-03-12)

scala> unionMember.to[Employee]()
res5: Employee = Employee(Micheal,41 Dunwoody St,1994-07-29,50000.0)
unionMember.to[Employee]()
// res5: Employee = Employee(Micheal,41 Dunwoody St,1994-07-29,50000.0)

scala> unionMember.to[Employee].set(salary = 60000.0)
res6: Employee = Employee(Micheal,41 Dunwoody St,1994-07-29,60000.0)
unionMember.to[Employee].set(salary = 60000.0)
// res6: Employee = Employee(Micheal,41 Dunwoody St,1994-07-29,60000.0)
```
Missing fields will fail the compilation
```scala
scala> case class People(name: String, address: String)
defined class People
case class People(name: String, address: String)
// defined class People

scala> val people = People("John", "49 Wall St.")
people: People = People(John,49 Wall St.)
val people = People("John", "49 Wall St.")
// people: People = People(John,49 Wall St.)
```
```scala
scala> people.to[Employee]() //missing DoB
<console>:22: error:
<console>:20: error:
You have not provided enough arguments to convert from People to Employee.
shapeless.HNil

Expand All @@ -125,7 +134,7 @@ scala> people.to[Employee]() //missing DoB
Wrong argument types will fail the compilation
```scala
scala> unionMember.to[Employee].set(salary = 60) //salary was input as Int rather than Double
<console>:22: error: One or more fields in shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("salary")],Int],shapeless.HNil] is not in Employee
<console>:20: error: One or more fields in shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("salary")],Int],shapeless.HNil] is not in Employee
error after rewriting to henkan.convert.Syntax.convert[UnionMember](unionMember).to[Employee].set.applyDynamicNamed("apply")(scala.Tuple2("salary", 60))
possible cause: maybe a wrong Dynamic method signature?
unionMember.to[Employee].set(salary = 60) //salary was input as Int rather than Double
Expand Down Expand Up @@ -166,15 +175,14 @@ implicit val fMap = myFieldReader[Map[String, Any]] // need this to recursively
Now you can extract any case classes with String or Int fields from the Map[String, Any] data

```scala
scala> extract[Option, MyParent](data)
res3: Option[MyParent] = Some(MyParent(parent,MyClass(a,2)))
extract[Option, MyParent](data)
// res3: Option[MyParent] = Some(MyParent(parent,MyClass(a,2)))
```

### Transform between case classes with optional field

`henkan.optional` provides some facility to transform between case classes with optional fields and ones with required fields.
Suppose you have two case classes: `Message` whose fields are optional and `Domain` whose fields are required. One use case for this
utility is when using scalaPB without required fields, you can create a set of domain model classes WITH required fields and use this facility to transform between the scalaPB generated case classes and your domain classes. Basically you can have two sets of data schemas encoded in case classes, a flexble one with every fields optional exposed as proto buf messages and an internal one with more stringent requirements.
`cats.optional` provides some facility to transform between case classes with optional fields and ones with required fields.
Suppose you have two case classes: `Message` whose fields are optional and `Domain` whose fields are required

```scala
case class Message(a: Option[String], b: Option[Int])
Expand All @@ -189,11 +197,11 @@ import henkan.optional.all._
```

```scala
scala> validate(Message(Some("a"), Some(2))).to[Domain]
res0: henkan.optional.ValidateFromOptional.Result[Domain] = Valid(Domain(a,2))
validate(Message(Some("a"), Some(2))).to[Domain]
// res0: henkan.optional.ValidateFromOptional.Result[Domain] = Valid(Domain(a,2))

scala> validate(Message(Some("a"), None)).to[Domain]
res1: henkan.optional.ValidateFromOptional.Result[Domain] = Invalid(NonEmptyList(RequiredFieldMissing(b)))
validate(Message(Some("a"), None)).to[Domain]
// res1: henkan.optional.ValidateFromOptional.Result[Domain] = Invalid(NonEmptyList(RequiredFieldMissing(b)))
```

The compilation will fail if the from case class doesn't have all fields the target case class needs
Expand All @@ -211,8 +219,8 @@ scala> validate(MessageWithMissingField(Some("a"))).to[Domain]

You can convert in the opposite direction as well
```scala
scala> from(Domain("a", 2)).toOptional[Message]
res4: Message = Message(Some(a),Some(2))
from(Domain("a", 2)).toOptional[Message]
// res4: Message = Message(Some(a),Some(2))
```

Note that if you from case class does not have all the fields the target class has, they will be set as `None`
Expand Down
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ lazy val examples = project

lazy val docs = project
.dependsOn(extract, convert, k, optional)
.settings(compile <<= (compile in Compile).dependsOn(tut))
.settings(test <<= (test in Test).dependsOn(tut))
.settings(compile := (compile in Compile).dependsOn(tut).value)
.settings(test := (test in Test).dependsOn(tut).value)
.settings(moduleName := "henkan-docs")
.settings(Dependencies.settings:_*)
.settings(tutSettings)
Expand Down
41 changes: 25 additions & 16 deletions docs/src/main/tut/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,34 +30,43 @@ limitations under the License.

## Modules

1. `henkan.convert` - transform between case classes, which minimize the need to manually using constructor to transform information from one case class to another.
### `henkan.convert`

Transform between case classes, which minimize the need to manually using constructor to transform information from one case class to another.

*Features*:

a. quick transformation when the source case class has all the fields the target case class has: e.g. `a.to[B]()`
1. quick transformation when the source case class has all the fields the target case class has: e.g. `a.to[B]()`

2. supplement (if source case class doesn't have a field) or override field values. e.g. `a.to[B].set(foo = "bar")`

b. supplement (if source case class doesn't have a field) or override field values. e.g. `a.to[B].set(foo = "bar")`
3. use the default values of the target case classes if needed

c. use the default values of the target case classes if needed
### `henkan.extract`

2. `henkan.extract` - transform between a runtime data type and a case class. Usually this type of transformation is done either manually or through some macro generated transformers. Using shapeless can achieve this as well, henkan is providing a generic transformer library on top of shapeless, which minimizes the boilerplate needed. However this part is also experimental and, as of now, limited than the macro solution.
Transform between a runtime data type and a case class. Usually this type of transformation is done either manually or through some macro generated transformers. Using shapeless can achieve this as well, henkan is providing a generic transformer library on top of shapeless, which minimizes the boilerplate needed. However this part is also experimental and, as of now, limited than the macro solution.

*Features*:

a. transform any runtime data type to an arbitrary Monad of taget case class - you just need to provide some `FieldReader`s that can read primitive values out of the runtime data type given a field name.
1. transform any runtime data type to an arbitrary Monad of taget case class - you just need to provide some `FieldReader`s that can read primitive values out of the runtime data type given a field name.

b. supports default value.
2. supports default value.

c. support recursive case classes, i.e. case class that has case class fields.
3. support recursive case classes, i.e. case class that has case class fields.

### Known issues for this feature
*Known issues for this feature*

* [Error when the last field is a nested class](https://github.com/kailuowang/henkan/issues/15)


3. `henkan.k` building blocks for generic function compositions.
### `henkan.k`

Building blocks for generic function compositions.

### `henkan.optional`

Conversion between case classes with optional fields and case class with required fields. One of the use cases for such conversions is conversion between scalaPB generated classes where most fields are Options and internal case classes where you have required fields.

4. `henkan.optional` conversion between case classes with optional fields and case class with required fields. One of the use cases for such conversions is conversion between scalaPB generated classes where most fields are Options and internal case classes where you have required fields.


## Get started
Expand Down Expand Up @@ -92,7 +101,7 @@ val unionMember = UnionMember("Micheal", "41 Dunwoody St", LocalDate.of(1994, 7,
```

Now use the henkan magic to transform between `UnionMember` and `Employee`
```tut
```tut:book
import henkan.convert.Syntax._
employee.to[UnionMember]()
Expand All @@ -103,7 +112,7 @@ unionMember.to[Employee].set(salary = 60000.0)
```
Missing fields will fail the compilation
```tut
```tut:book
case class People(name: String, address: String)
val people = People("John", "49 Wall St.")
Expand Down Expand Up @@ -151,7 +160,7 @@ implicit val fMap = myFieldReader[Map[String, Any]] // need this to recursively

Now you can extract any case classes with String or Int fields from the Map[String, Any] data

```tut
```tut:book
extract[Option, MyParent](data)
```

Expand All @@ -172,7 +181,7 @@ import cats.implicits._
import henkan.optional.all._
```

```tut
```tut:book
validate(Message(Some("a"), Some(2))).to[Domain]
validate(Message(Some("a"), None)).to[Domain]
Expand All @@ -189,7 +198,7 @@ validate(MessageWithMissingField(Some("a"))).to[Domain]
```

You can convert in the opposite direction as well
```tut
```tut:book
from(Domain("a", 2)).toOptional[Message]
```

Expand Down
6 changes: 5 additions & 1 deletion project/Common.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ object Common {
"-unchecked",
// "-Xlog-implicits",
"-Xlint"
)
) ++ (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 12)) =>
Seq("-Ypartial-unification")
case _ => Seq()
})
)
}
16 changes: 10 additions & 6 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,34 @@ import sbt._

object Dependencies {
object Versions {
val specs2 = "3.6.6"
val specs2 = "3.8.9"
}

val shapeless = Seq("com.chuusai" %% "shapeless" % "2.3.2")

val kittens = Seq("org.typelevel" %% "kittens" % "1.0.0-M7")
val kittens = Seq("org.typelevel" %% "kittens" % "1.0.0-M9")

val test = Seq(
"org.specs2" %% "specs2-core" % Versions.specs2 % "test",
"org.specs2" %% "specs2-mock" % Versions.specs2 % "test"
)

val withKittens = Seq(
libraryDependencies ++= kittens,
addCompilerPlugin("com.milessabin" % "si2712fix-plugin" % "1.2.0" cross CrossVersion.full)
libraryDependencies ++= kittens ++ (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, scalaMajor)) if scalaMajor < 12 =>
Seq(compilerPlugin("com.milessabin" % "si2712fix-plugin" % "1.1.0" cross CrossVersion.patch))
case _ => Seq()
})
)

val commonSettings = Seq(
scalaVersion in ThisBuild := "2.11.8",
scalaVersion := "2.12.1",
crossScalaVersions := Seq( "2.11.8", scalaVersion.value),
resolvers ++= Seq(
Resolver.sonatypeRepo("releases"),
Resolver.bintrayRepo("scalaz", "releases")
),
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.0")
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.3")
)

val settings = commonSettings ++ Seq(
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.11
sbt.version=0.13.13
4 changes: 2 additions & 2 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0")

addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.1")

addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0-RC2")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0")

addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.1.0")

addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.1")
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.8")

0 comments on commit e8f0eb2

Please sign in to comment.