Skip to content

Commit

Permalink
Merge branch 'master' of github.com:lloydmeta/enumeratum
Browse files Browse the repository at this point in the history
  • Loading branch information
lloydmeta committed Oct 15, 2016
2 parents 68377ba + b02cda8 commit 0ceba3a
Show file tree
Hide file tree
Showing 10 changed files with 459 additions and 4 deletions.
70 changes: 67 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Integrations are available for:
- [Circe](https://github.com/travisbrown/circe): JVM and ScalaJS
- [UPickle](http://www.lihaoyi.com/upickle-pprint/upickle/): JVM and ScalaJS
- [ReactiveMongo BSON](http://reactivemongo.org/releases/0.11/documentation/bson/overview.html): JVM only
- [Argonaut](http://www.argonaut.io): JVM only

### Table of Contents

Expand All @@ -46,8 +47,9 @@ Integrations are available for:
5. [Circe integration](#circe)
6. [UPickle integration](#upickle)
7. [ReactiveMongo BSON integration](#reactivemongo-bson)
8. [Slick integration](#slick-integration)
9. [Benchmarking](#benchmarking)
8. [Argonaut integration](#argonaut)
9. [Slick integration](#slick-integration)
10. [Benchmarking](#benchmarking)


## Quick start
Expand Down Expand Up @@ -661,7 +663,69 @@ val reader = implicitly[BSONReader[BSONValue, BsonDrinks]]
assert(reader.read(BSONInteger(3)) == BsonDrinks.Cola)
```

### Slick integration
## Argonaut

### SBT

To use enumeratum with [Argonaut](http://www.argonaut.io):

```scala
libraryDependencies ++= Seq(
"com.beachape" %% "enumeratum" % enumeratumVersion,
"com.beachape" %% "enumeratum-argonaut" % enumeratumVersion
)
```

### Usage

#### Enum

```scala
import enumeratum._

sealed trait TrafficLight extends EnumEntry
object TrafficLight extends Enum[TrafficLight] with ArgonautEnum[TrafficLight] {
case object Red extends TrafficLight
case object Yellow extends TrafficLight
case object Green extends TrafficLight
val values = findValues
}

import argonaut._
import Argonaut._

TrafficLight.values.foreach { entry =>
assert(entry.asJson == entry.entryName.asJson)
}

```

#### ValueEnum

```scala
import enumeratum.values._

sealed abstract class ArgonautDevice(val value: Short) extends ShortEnumEntry
case object ArgonautDevice
extends ShortEnum[ArgonautDevice]
with ShortArgonautEnum[ArgonautDevice] {
case object Phone extends ArgonautDevice(1)
case object Laptop extends ArgonautDevice(2)
case object Desktop extends ArgonautDevice(3)
case object Tablet extends ArgonautDevice(4)

val values = findValues
}

import argonaut._
import Argonaut._

ArgonautDevice.values.foreach { item =>
assert(item.asJson == item.value.asJson)
}
```

## Slick integration

[Slick](http://slick.lightbend.com) doesn't have a separate integration at the moment. You just have to provide a `MappedColumnType` for each database column that should be represented as an enum on the Scala side.

Expand Down
15 changes: 14 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ lazy val root =
enumeratumUPickleJvm,
enumeratumCirceJs,
enumeratumCirceJvm,
enumeratumReactiveMongoBson)
enumeratumReactiveMongoBson,
enumeratumArgonaut)

lazy val core = crossProject
.crossType(CrossType.Pure)
Expand Down Expand Up @@ -173,6 +174,18 @@ lazy val enumeratumCirce = crossProject
lazy val enumeratumCirceJs = enumeratumCirce.js
lazy val enumeratumCirceJvm = enumeratumCirce.jvm

lazy val enumeratumArgonaut =
Project(id = "enumeratum-argonaut",
base = file("enumeratum-argonaut"),
settings = commonWithPublishSettings)
.settings(
libraryDependencies ++= Seq(
"io.argonaut" %% "argonaut" % "6.1"
)
)
.settings(testSettings: _*)
.dependsOn(coreJvm % "test->test;compile->compile")

lazy val commonSettings = Seq(
organization := "com.beachape",
version := theVersion,
Expand Down
14 changes: 14 additions & 0 deletions enumeratum-argonaut/src/main/scala/enumeratum/ArgonautEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package enumeratum

import argonaut._

/**
* Created by alonsodomin on 14/10/2016.
*/
trait ArgonautEnum[A <: EnumEntry] { this: Enum[A] =>

implicit val argonautEncoder: EncodeJson[A] = Argonauter.encoder(this)

implicit val argonautDecoder: DecodeJson[A] = Argonauter.decoder(this)

}
45 changes: 45 additions & 0 deletions enumeratum-argonaut/src/main/scala/enumeratum/Argonauter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package enumeratum

import argonaut._
import Argonaut._

/**
* Created by alonsodomin on 14/10/2016.
*/
object Argonauter {

private def encoder0[A <: EnumEntry](f: A => String): EncodeJson[A] =
stringEncoder.contramap(f)

def encoder[A <: EnumEntry](enum: Enum[A]): EncodeJson[A] =
encoder0[A](_.entryName)

def encoderLowercase[A <: EnumEntry](enum: Enum[A]): EncodeJson[A] =
encoder0[A](_.entryName.toLowerCase)

def encoderUppercase[A <: EnumEntry](enum: Enum[A]): EncodeJson[A] =
encoder0[A](_.entryName.toUpperCase)

private def decoder0[A <: EnumEntry](enum: Enum[A])(f: String => Option[A]): DecodeJson[A] =
DecodeJson { cursor =>
stringDecoder(cursor).flatMap { enumStr =>
f(enumStr) match {
case Some(a) => okResult(a)
case _ => failResult(s"$enumStr' is not a member of enum $enum", cursor.history)
}
}
}

def decoder[A <: EnumEntry](enum: Enum[A]): DecodeJson[A] =
decoder0(enum)(enum.withNameOption)

def decoderLowercaseOnly[A <: EnumEntry](enum: Enum[A]): DecodeJson[A] =
decoder0(enum)(enum.withNameLowercaseOnlyOption)

def decoderUppercaseOnly[A <: EnumEntry](enum: Enum[A]): DecodeJson[A] =
decoder0(enum)(enum.withNameUppercaseOnlyOption)

private val stringEncoder = implicitly[EncodeJson[String]]
private val stringDecoder = implicitly[DecodeJson[String]]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package enumeratum.values

import argonaut._
import Argonaut._

/**
* Created by alonsodomin on 14/10/2016.
*/
sealed trait ArgonautValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] {
this: ValueEnum[ValueType, EntryType] =>

implicit def argonautEncoder: EncodeJson[EntryType]
implicit def argonautDecoder: DecodeJson[EntryType]

}

/**
* ArgonautEnum for IntEnumEntry
*/
trait IntArgonautEnum[EntryType <: IntEnumEntry] extends ArgonautValueEnum[Int, EntryType] {
this: ValueEnum[Int, EntryType] =>

implicit val argonautEncoder: EncodeJson[EntryType] = Argonauter.encoder(this)
implicit val argonautDecoder: DecodeJson[EntryType] = Argonauter.decoder(this)
}

/**
* ArgonautEnum for LongEnumEntry
*/
trait LongArgonautEnum[EntryType <: LongEnumEntry] extends ArgonautValueEnum[Long, EntryType] {
this: ValueEnum[Long, EntryType] =>

implicit val argonautEncoder: EncodeJson[EntryType] = Argonauter.encoder(this)
implicit val argonautDecoder: DecodeJson[EntryType] = Argonauter.decoder(this)
}

/**
* ArgonautEnum for ShortEnumEntry
*/
trait ShortArgonautEnum[EntryType <: ShortEnumEntry] extends ArgonautValueEnum[Short, EntryType] {
this: ValueEnum[Short, EntryType] =>

implicit val argonautEncoder: EncodeJson[EntryType] = Argonauter.encoder(this)
implicit val argonautDecoder: DecodeJson[EntryType] = Argonauter.decoder(this)
}

/**
* ArgonautEnum for StringEnumEntry
*/
trait StringArgonautEnum[EntryType <: StringEnumEntry]
extends ArgonautValueEnum[String, EntryType] { this: ValueEnum[String, EntryType] =>

implicit val argonautEncoder: EncodeJson[EntryType] = Argonauter.encoder(this)
implicit val argonautDecoder: DecodeJson[EntryType] = Argonauter.decoder(this)
}

/**
* ArgonautEnum for CharEnumEntry
*/
trait CharArgonautEnum[EntryType <: CharEnumEntry] extends ArgonautValueEnum[Char, EntryType] {
this: ValueEnum[Char, EntryType] =>

implicit val argonautEncoder: EncodeJson[EntryType] = Argonauter.encoder(this)
implicit val argonautDecoder: DecodeJson[EntryType] = Argonauter.decoder(this)
}

/**
* ArgonautEnum for ByteEnumEntry
*/
trait ByteArgonautEnum[EntryType <: ByteEnumEntry] extends ArgonautValueEnum[Byte, EntryType] {
this: ValueEnum[Byte, EntryType] =>

implicit val argonautEncoder: EncodeJson[EntryType] = Argonauter.encoder(this)
implicit val argonautDecoder: DecodeJson[EntryType] = Argonauter.decoder(this)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package enumeratum.values

import argonaut._
import Argonaut._

/**
* Created by alonsodomin on 14/10/2016.
*/
object Argonauter {

def encoder[ValueType: EncodeJson, EntryType <: ValueEnumEntry[ValueType]](
enum: ValueEnum[ValueType, EntryType]): EncodeJson[EntryType] = {
val encodeValue = implicitly[EncodeJson[ValueType]]
EncodeJson { entry =>
encodeValue(entry.value)
}
}

def decoder[ValueType: DecodeJson, EntryType <: ValueEnumEntry[ValueType]](
enum: ValueEnum[ValueType, EntryType]): DecodeJson[EntryType] = {
val decodeValue = implicitly[DecodeJson[ValueType]]
DecodeJson { cursor =>
decodeValue(cursor).flatMap { value =>
enum.withValueOpt(value) match {
case Some(entry) => okResult(entry)
case _ => failResult(s"$value is not a member of enum $enum", cursor.history)
}
}
}
}

}
19 changes: 19 additions & 0 deletions enumeratum-argonaut/src/main/scala/enumeratum/values/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package enumeratum

import argonaut.Argonaut._
import argonaut.{DecodeJson, EncodeJson}

/**
* Created by alonsodomin on 15/10/2016.
*/
package object values {

implicit val argonautByteEncoder: EncodeJson[Byte] = EncodeJson { byte =>
jNumber(byte.toShort)
}

implicit val argonautByteDecoder: DecodeJson[Byte] = DecodeJson { cursor =>
cursor.as[Short].map(_.toByte)
}

}
63 changes: 63 additions & 0 deletions enumeratum-argonaut/src/test/scala/enumeratum/ArgonautSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package enumeratum

import org.scalatest.{FunSpec, Matchers}

import argonaut._
import Argonaut._

/**
* Created by alonsodomin on 14/10/2016.
*/
class ArgonautSpec extends FunSpec with Matchers {

describe("to JSON") {
it("should work") {
TrafficLight.values.foreach { value =>
value.asJson shouldBe value.entryName.asJson
}
}

it("should work for lower case") {
TrafficLight.values.foreach { value =>
value.asJson(Argonauter.encoderLowercase(TrafficLight)) shouldBe value.entryName.toLowerCase.asJson
}
}

it("should work for upper case") {
TrafficLight.values.foreach { value =>
value.asJson(Argonauter.encoderUppercase(TrafficLight)) shouldBe value.entryName.toUpperCase.asJson
}
}
}

describe("from JSON") {
it("should parse enum members when given proper encoding") {
TrafficLight.values.foreach { value =>
value.entryName.asJson.as[TrafficLight] shouldBe okResult(value)
}
}

it("should parse enum members when given proper encoding for lower case") {
TrafficLight.values.foreach { value =>
value.entryName.toLowerCase.asJson
.as[TrafficLight](Argonauter.decoderLowercaseOnly(TrafficLight)) shouldBe okResult(value)
}
}

it("should parse enum members when given proper encoding for upper case") {
TrafficLight.values.foreach { value =>
value.entryName.toUpperCase.asJson
.as[TrafficLight](Argonauter.decoderUppercaseOnly(TrafficLight)) shouldBe okResult(value)
}
}

it("should fail to parse random JSON values to members") {
val results = Seq("XXL".asJson, Int.MaxValue.asJson).map(_.as[TrafficLight])
results.foreach { res =>
res.result.isLeft shouldBe true
res.history.map(_.toList) shouldBe Some(Nil)
}
}
}

}
13 changes: 13 additions & 0 deletions enumeratum-argonaut/src/test/scala/enumeratum/TrafficLight.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package enumeratum

/**
* Created by alonsodomin on 14/10/2016.
*/
sealed trait TrafficLight extends EnumEntry
object TrafficLight extends Enum[TrafficLight] with ArgonautEnum[TrafficLight] {
case object Red extends TrafficLight
case object Yellow extends TrafficLight
case object Green extends TrafficLight

val values = findValues
}
Loading

0 comments on commit 0ceba3a

Please sign in to comment.