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 Sep 24, 2016
2 parents 387bb2f + 6fcf6cb commit ccbbbde
Show file tree
Hide file tree
Showing 26 changed files with 437 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Greeting.withName("SHOUT_GOOD_BYE")
### ValueEnum

Asides from enumerations that resolve members from `String` _names_, Enumeratum also supports `ValueEnum`s, enums that resolve
members from simple _values_ like `Int`, `Long`, `Short`, and `String` (without support for runtime transformations).
members from simple _values_ like `Int`, `Long`, `Short`, `Char`, `Byte`, and `String` (without support for runtime transformations).

These enums are not modelled after `Enumeration` from standard lib, and therefore have the added ability to make sure, at compile-time,
that multiple members do not share the same value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,19 @@ trait StringCirceEnum[EntryType <: StringEnumEntry] extends CirceValueEnum[Strin
implicit val circeEncoder = Circe.encoder(this)
implicit val circeDecoder = Circe.decoder(this)
}

/**
* CirceEnum for CharEnumEntry
*/
trait CharCirceEnum[EntryType <: CharEnumEntry] extends CirceValueEnum[Char, EntryType] { this: ValueEnum[Char, EntryType] =>
implicit val circeEncoder = Circe.encoder(this)
implicit val circeDecoder = Circe.decoder(this)
}

/**
* CirceEnum for ByteEnumEntry
*/
trait ByteCirceEnum[EntryType <: ByteEnumEntry] extends CirceValueEnum[Byte, EntryType] { this: ValueEnum[Byte, EntryType] =>
implicit val circeEncoder = Circe.encoder(this)
implicit val circeDecoder = Circe.decoder(this)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class CirceValueEnumSpec extends FunSpec with Matchers {
testCirceEnum("ShortCirceEnum", CirceDrinks)
testCirceEnum("IntCirceEnum", CirceLibraryItem)
testCirceEnum("StringCirceEnum", CirceOperatingSystem)
testCirceEnum("CharEnum", CirceAlphabet)
testCirceEnum("ByteEnum", CirceBites)
testCirceEnum("IntCirceEnum with val value members", CirceMovieGenre)

// Test method that generates tests for most primitve-based ValueEnums when given a simple descriptor and the enum
Expand Down Expand Up @@ -124,3 +126,27 @@ case object CirceMovieGenre extends IntEnum[CirceMovieGenre] with IntCirceEnum[C
val values = findValues

}

sealed abstract class CirceAlphabet(val value: Char) extends CharEnumEntry

case object CirceAlphabet extends CharEnum[CirceAlphabet] with CharCirceEnum[CirceAlphabet] {

case object A extends CirceAlphabet('A')
case object B extends CirceAlphabet('B')
case object C extends CirceAlphabet('C')
case object D extends CirceAlphabet('D')

val values = findValues

}

sealed abstract class CirceBites(val value: Byte) extends ByteEnumEntry

object CirceBites extends ByteEnum[CirceBites] with ByteCirceEnum[CirceBites] {
val values = findValues

case object OneByte extends CirceBites(1)
case object TwoByte extends CirceBites(2)
case object ThreeByte extends CirceBites(3)
case object FourByte extends CirceBites(4)
}
58 changes: 57 additions & 1 deletion enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ object StringEnum {
}

/**
* Value enum with [[ShortEnumEntry]] entries
* Value enum with [[StringEnumEntry]] entries
*
* This is similar to [[enumeratum.Enum]], but different in that values must be
* literal values. This restraint allows us to enforce uniqueness at compile time.
Expand All @@ -148,3 +148,59 @@ trait StringEnum[A <: StringEnumEntry] extends ValueEnum[String, A] {
final protected def findValues: IndexedSeq[A] = macro ValueEnumMacros.findStringValueEntriesImpl[A]
}

object ByteEnum {

/**
* Materializes a ByteEnum for an in-scope ByteEnumEntry
*/
implicit def materialiseByteValueEnum[EntryType <: ByteEnumEntry]: ByteEnum[EntryType] = macro EnumMacros.materializeEnumImpl[EntryType]

}

/**
* Value enum with [[ByteEnumEntry]] entries
*
* This is similar to [[enumeratum.Enum]], but different in that values must be
* literal values. This restraint allows us to enforce uniqueness at compile time.
*
* Note that uniqueness is only guaranteed if you do not do any runtime string manipulation on values.
*/
trait ByteEnum[A <: ByteEnumEntry] extends ValueEnum[Byte, A] {

/**
* Method that returns a Seq of [[A]] objects that the macro was able to find.
*
* You will want to use this in some way to implement your [[values]] method. In fact,
* if you aren't using this method...why are you even bothering with this lib?
*/
final protected def findValues: IndexedSeq[A] = macro ValueEnumMacros.findByteValueEntriesImpl[A]
}

object CharEnum {

/**
* Materializes a CharEnum for an in-scope CharEnumEntry
*/
implicit def materialiseCharValueEnum[EntryType <: CharEnumEntry]: CharEnum[EntryType] = macro EnumMacros.materializeEnumImpl[EntryType]

}

/**
* Value enum with [[CharEnumEntry]] entries
*
* This is similar to [[enumeratum.Enum]], but different in that values must be
* literal values. This restraint allows us to enforce uniqueness at compile time.
*
* Note that uniqueness is only guaranteed if you do not do any runtime string manipulation on values.
*/
trait CharEnum[A <: CharEnumEntry] extends ValueEnum[Char, A] {

/**
* Method that returns a Seq of [[A]] objects that the macro was able to find.
*
* You will want to use this in some way to implement your [[values]] method. In fact,
* if you aren't using this method...why are you even bothering with this lib?
*/
final protected def findValues: IndexedSeq[A] = macro ValueEnumMacros.findCharValueEntriesImpl[A]
}

Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,12 @@ abstract class ShortEnumEntry extends ValueEnumEntry[Short]
*/
abstract class StringEnumEntry extends ValueEnumEntry[String]

/**
* Value Enum Entry parent class for [[Byte]] valued entries
*/
abstract class ByteEnumEntry extends ValueEnumEntry[Byte]

/**
* Value Enum Entry parent class for [[Char]] valued entries
*/
abstract class CharEnumEntry extends ValueEnumEntry[Char]
19 changes: 19 additions & 0 deletions enumeratum-core/src/test/scala/enumeratum/values/Alphabet.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package enumeratum.values

/**
* Created by Lloyd on 9/24/16.
*
* Copyright 2016
*/
sealed abstract class Alphabet(val value: Char) extends CharEnumEntry

case object Alphabet extends CharEnum[Alphabet] {

val values = findValues

case object A extends Alphabet('A')
case object B extends Alphabet('B')
case object C extends Alphabet('C')
case object D extends Alphabet('D')

}
17 changes: 17 additions & 0 deletions enumeratum-core/src/test/scala/enumeratum/values/Bites.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package enumeratum.values

/**
* Created by Lloyd on 9/24/16.
*
* Copyright 2016
*/
sealed abstract class Bites(val value: Byte) extends ByteEnumEntry

object Bites extends ByteEnum[Bites] {
val values = findValues

case object OneByte extends Bites(1)
case object TwoByte extends Bites(2)
case object ThreeByte extends Bites(3)
case object FourByte extends Bites(4)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class ValueEnumSpec extends FunSpec with Matchers with ValueEnumHelpers {
testNumericEnum("ShortEnum", Drinks)
testNumericEnum("LongEnum", ContentType)
testEnum("StringEnum", OperatingSystem, Seq("windows-phone"))
testEnum("CharEnum", Alphabet, Seq('Z'))
testEnum("ByteEnum", Bites, Seq(10).map(_.toByte))
testNumericEnum("when using val members in the body", MovieGenre)
testNumericEnum("LongEnum that is nesting an IntEnum", Animal)
testNumericEnum("IntEnum that is nested inside a LongEnum", Animal.Mammalian)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,16 @@ object EnumFormats {
Format(reads(enum), writes(enum))
}

/**
* Format for Char
*/
implicit val charFormat: Format[Char] = new Format[Char] {
def writes(o: Char): JsValue = JsString(s"$o")

def reads(json: JsValue): JsResult[Char] = json match {
case JsString(s) if s.length == 1 => JsSuccess(s.charAt(0))
case _ => JsError("error.expected.singleChar")
}
}

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

import play.api.libs.json.Format
import play.api.libs.json._
import EnumFormats.charFormat

/**
* Created by Lloyd on 4/13/16.
Expand Down Expand Up @@ -43,4 +44,18 @@ trait ShortPlayJsonValueEnum[EntryType <: ShortEnumEntry] extends PlayJsonValueE
*/
trait StringPlayJsonValueEnum[EntryType <: StringEnumEntry] extends PlayJsonValueEnum[String, EntryType] { this: StringEnum[EntryType] =>
implicit val format: Format[EntryType] = EnumFormats.formats(this)
}

/**
* Enum implementation for Char enum members that contains an implicit Play JSON Format
*/
trait CharPlayJsonValueEnum[EntryType <: CharEnumEntry] extends PlayJsonValueEnum[Char, EntryType] { this: CharEnum[EntryType] =>
implicit val format: Format[EntryType] = EnumFormats.formats(this)
}

/**
* Enum implementation for Byte enum members that contains an implicit Play JSON Format
*/
trait BytePlayJsonValueEnum[EntryType <: ByteEnumEntry] extends PlayJsonValueEnum[Byte, EntryType] { this: ByteEnum[EntryType] =>
implicit val format: Format[EntryType] = EnumFormats.formats(this)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package enumeratum.values

import org.scalatest._
import play.api.libs.json.JsString
import EnumFormats._
import play.api.libs.json.{ JsNumber, JsString }

/**
* Created by Lloyd on 4/13/16.
Expand All @@ -16,6 +17,8 @@ class EnumFormatsSpec extends FunSpec with Matchers with EnumJsonFormatHelpers {
testNumericReads("LongEnum", ContentType)
testNumericReads("ShortEnum", Drinks)
testReads("StringEnum", OperatingSystem, JsString)
testReads("CharEnum", Alphabet, { c: Char => JsString(s"$c") })
testReads("ByteEnum", Bites, { b: Byte => JsNumber(b) })

}

Expand All @@ -25,6 +28,8 @@ class EnumFormatsSpec extends FunSpec with Matchers with EnumJsonFormatHelpers {
testNumericWrites("LongEnum", ContentType)
testNumericWrites("ShortEnum", Drinks)
testWrites("StringEnum", OperatingSystem, JsString)
testWrites("CharEnum", Alphabet, { c: Char => JsString(s"$c") })
testWrites("ByteEnum", Bites, { b: Byte => JsNumber(b) })

}

Expand All @@ -34,6 +39,7 @@ class EnumFormatsSpec extends FunSpec with Matchers with EnumJsonFormatHelpers {
testNumericFormats("LongEnum", ContentType)
testNumericFormats("ShortEnum", Drinks)
testFormats("StringEnum", OperatingSystem, JsString)
testFormats("ByteEnum", Bites, { b: Byte => JsNumber(b.toInt) })
testNumericFormats("PlayJsonValueEnum", JsonDrinks, Some(JsonDrinks.format))

}
Expand Down
10 changes: 10 additions & 0 deletions enumeratum-play/src/main/scala/enumeratum/values/Forms.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,14 @@ object Forms {
}
}

/**
* Taken from Play 2.4.x implementation
*/
private[values] val charFormatter: Formatter[Char] = new Formatter[Char] {
def bind(key: String, data: Map[String, String]) =
data.get(key).filter(s => s.length == 1 && s != " ").map(s => Right(s.charAt(0))).getOrElse(
Left(Seq(FormError(key, "error.required", Nil)))
)
def unbind(key: String, value: Char) = Map(key -> value.toString)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package enumeratum.values

import play.api.data.format.{ Formatter, Formats }
import play.api.data.Mapping
import play.api.data.format.{ Formats, Formatter }
import play.api.data.{ FormError, Mapping }

/**
* Created by Lloyd on 4/13/16.
Expand Down Expand Up @@ -51,4 +51,18 @@ trait ShortPlayFormValueEnum[EntryType <: ShortEnumEntry] extends PlayFormValueE
*/
trait StringPlayFormValueEnum[EntryType <: StringEnumEntry] extends PlayFormValueEnum[String, EntryType] { this: StringEnum[EntryType] =>
protected val baseFormatter: Formatter[String] = Formats.stringFormat
}

/**
* Form Bindable implicits for CharEnum
*/
trait CharPlayFormValueEnum[EntryType <: CharEnumEntry] extends PlayFormValueEnum[Char, EntryType] { this: CharEnum[EntryType] =>
protected val baseFormatter: Formatter[Char] = Forms.charFormatter
}

/**
* Form Bindable implicits for ByteEnum
*/
trait BytePlayFormValueEnum[EntryType <: ByteEnumEntry] extends PlayFormValueEnum[Byte, EntryType] { this: ByteEnum[EntryType] =>
protected val baseFormatter: Formatter[Byte] = Formats.byteFormat
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,18 @@ trait ShortPlayPathBindableValueEnum[EntryType <: ShortEnumEntry] extends PlayPa
*/
trait StringPlayPathBindableValueEnum[EntryType <: StringEnumEntry] extends PlayPathBindableValueEnum[String, EntryType] { this: StringEnum[EntryType] =>
implicit val pathBindable: PathBindable[EntryType] = UrlBinders.pathBinder(this)(PathBindable.bindableString)
}

/**
* Path Bindable implicits for CharEnum
*/
trait CharPlayPathBindableValueEnum[EntryType <: CharEnumEntry] extends PlayPathBindableValueEnum[Char, EntryType] { this: CharEnum[EntryType] =>
implicit val pathBindable: PathBindable[EntryType] = UrlBinders.pathBinder(this)(PathBindable.bindableChar)
}

/**
* Path Bindable implicits for ByteEnum
*/
trait BytePlayPathBindableValueEnum[EntryType <: ByteEnumEntry] extends PlayPathBindableValueEnum[Byte, EntryType] { this: ByteEnum[EntryType] =>
implicit val pathBindable: PathBindable[EntryType] = UrlBinders.pathBinder(this)(PathBindable.bindableInt.transform(_.toByte, _.toInt))
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,18 @@ trait ShortPlayQueryBindableValueEnum[EntryType <: ShortEnumEntry] extends PlayQ
*/
trait StringPlayQueryBindableValueEnum[EntryType <: StringEnumEntry] extends PlayQueryBindableValueEnum[String, EntryType] { this: StringEnum[EntryType] =>
implicit val queryBindable: QueryStringBindable[EntryType] = UrlBinders.queryBinder(this)(QueryStringBindable.bindableString)
}

/**
* Query Bindable implicits for CharEnum
*/
trait CharPlayQueryBindableValueEnum[EntryType <: CharEnumEntry] extends PlayQueryBindableValueEnum[Char, EntryType] { this: CharEnum[EntryType] =>
implicit val queryBindable: QueryStringBindable[EntryType] = UrlBinders.queryBinder(this)(QueryStringBindable.bindableChar)
}

/**
* Query Bindable implicits for ByteEnum
*/
trait BytePlayQueryBindableValueEnum[EntryType <: ByteEnumEntry] extends PlayQueryBindableValueEnum[Byte, EntryType] { this: ByteEnum[EntryType] =>
implicit val queryBindable: QueryStringBindable[EntryType] = UrlBinders.queryBinder(this)(QueryStringBindable.bindableInt.transform(_.toByte, _.toInt))
}
Loading

0 comments on commit ccbbbde

Please sign in to comment.