• Stars
    star
    1,163
  • Rank 38,223 (Top 0.8 %)
  • Language
    Scala
  • License
    MIT License
  • Created over 9 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A type-safe, reflection-free, powerful enumeration implementation for Scala with exhaustive pattern match warnings and helpful integrations.

Enumeratum Continuous integration codecov Codacy Badge Maven Central Scala.js Join the chat at https://gitter.im/lloydmeta/enumeratum

Enumeratum is a type-safe and powerful enumeration implementation for Scala that offers exhaustive pattern match warnings, integrations with popular Scala libraries, and idiomatic usage that won't break your IDE. It aims to be similar enough to Scala's built in Enumeration to be easy-to-use and understand while offering more flexibility, type-safety (see this blog post describing erasure on Scala's Enumeration), and richer enum values without having to maintain your own collection of values.

Enumeratum has the following niceties:

  • Zero dependencies
  • Performant: Faster thanEnumeration in the standard library (see benchmarks)
  • Allows your Enum members to be full-fledged normal objects with methods, values, inheritance, etc.
  • ValueEnums that map to various primitive values and have compile-time uniqueness constraints.
  • Idiomatic: you're very clearly still writing Scala, and no funny colours in your IDE means less cognitive overhead for your team
  • Simplicity; most of the complexity in this lib is in its macro, and the macro is fairly simple conceptually
  • No usage of reflection at runtime. This may also help with performance but it means Enumeratum is compatible with ScalaJS and other environments where reflection is a best effort (such as Android)
  • No usage of synchronized, which may help with performance and deadlocks prevention
  • All magic happens at compile-time so you know right away when things go awry
  • Comprehensive automated testing to make sure everything is in tip-top shape

Enumeratum is published for Scala 2.12.x, 2.13.x, 3.x as well as ScalaJS and ScalaNative.

Note that there are a couple of limitations for 3.x:

  • All "immediate" parent types of enum entries need to be sealed (reasoning here)
  • The -Yretain-trees Scalac option must be set when using ValueEnums

Integrations are available for:

Table of Contents

  1. Quick start
  2. More examples
  3. ScalaJS
  4. Play integration
  5. Play JSON integration
  6. Circe integration
  7. ReactiveMongo BSON integration
  8. Argonaut integration
  9. Json4s integration
  10. Slick integration
  11. ScalaCheck
  12. Quill integration
  13. Cats integration
  14. Doobie integration
  15. Anorm integration
  16. Benchmarking
  17. Publishing

Quick start

SBT

Maven Central

In build.sbt, set the Enumeratum version in a variable (for the latest version, set val enumeratumVersion = the version you see in the Maven badge above).

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum" % enumeratumVersion
)

Enumeratum has different integrations that can be added to your build à la carte. For more info, see the respective sections in the Table of Contents

Usage

Using Enumeratum is simple. Just declare your own sealed trait or class A that extends EnumEntry and implement it as case objects inside an object that extends from Enum[A] as shown below.

import enumeratum._

sealed trait Greeting extends EnumEntry

object Greeting extends Enum[Greeting] {

  /*
   `findValues` is a protected method that invokes a macro to find all `Greeting` object declarations inside an `Enum`

   You use it to implement the `val values` member
  */
  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

// Object Greeting has a `withName(name: String)` method
Greeting.withName("Hello")
// => res0: Greeting = Hello

Greeting.withName("Haro")
// => java.lang.NoSuchElementException: Haro is not a member of Enum (Hello, GoodBye, Hi, Bye)

// A safer alternative would be to use `withNameOption(name: String)` method which returns an Option[Greeting]
Greeting.withNameOption("Hello")
// => res1: Option[Greeting] = Some(Hello)

Greeting.withNameOption("Haro")
// => res2: Option[Greeting] = None

// It is also possible to use strings case insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello

Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)

// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello

Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None

// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello

Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)

Note that by default, findValues will return a Seq with the enum members listed in written-order (relevant if you want to use the indexOf method).

Enum members found in nested objects will be included by findValues as well, and will appear in the order they are written in the companion object, top to bottom. Note that enum members declared in traits or classes will not be discovered by findValues. For example:

sealed trait Nesting extends EnumEntry
object Nesting extends Enum[Nesting] {
  val values = findValues

  case object Hello extends Nesting
  object others {
    case object GoodBye extends Nesting
  }
  case object Hi extends Nesting
  class InnerClass {
    case object NotFound extends Nesting
  }
}

Nesting.values
// => res0: scala.collection.immutable.IndexedSeq[Nesting] = Vector(Hello, GoodBye, Hi)

For an interactive demo, checkout this Scastie snippet.

More examples

Enum

Continuing from the Greeting enum declared in the quick-start section:

import Greeting._

def tryMatching(v: Greeting): Unit = v match {
  case Hello   => println("Hello")
  case GoodBye => println("GoodBye")
  case Hi      => println("Hi")
}

/**
Pattern match warning ...

<console>:24: warning: match may not be exhaustive.
It would fail on the following input: Bye
       def tryMatching(v: Greeting): Unit = v match {

*/

Greeting.indexOf(Bye)
// => res2: Int = 3

The name is taken from the toString method of the particular EnumEntry. This behavior can be changed in two ways.

Manual override of name

The first way to change the name behaviour is to manually override the def entryName: String method.

import enumeratum._

sealed abstract class State(override val entryName: String) extends EnumEntry

object State extends Enum[State] {

   val values = findValues

   case object Alabama extends State("AL")
   case object Alaska  extends State("AK")
   // and so on and so forth.
}

import State._

State.withName("AL")

Mixins to override the name

The second way to override the name behaviour is to mixin the stackable traits provided for common string conversions, Snakecase, UpperSnakecase, CapitalSnakecase, Hyphencase, UpperHyphencase, CapitalHyphencase, Dotcase, UpperDotcase, CapitalDotcase, Words, UpperWords, CapitalWords, Camelcase, LowerCamelcase, Uppercase, Lowercase, and Uncapitalised.

import enumeratum._
import enumeratum.EnumEntry._

sealed trait Greeting extends EnumEntry with Snakecase

object Greeting extends Enum[Greeting] {

  val values = findValues

  case object Hello        extends Greeting
  case object GoodBye      extends Greeting
  case object ShoutGoodBye extends Greeting with Uppercase

}

Greeting.withName("hello")
Greeting.withName("good_bye")
Greeting.withName("SHOUT_GOOD_BYE")

ValueEnum

Asides from enumerations that resolve members from String names, Enumeratum also supports ValueEnums, enums that resolve 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.

import enumeratum.values._

sealed abstract class LibraryItem(val value: Int, val name: String) extends IntEnumEntry

object LibraryItem extends IntEnum[LibraryItem] {


  case object Book     extends LibraryItem(value = 1, name = "book")
  case object Movie    extends LibraryItem(name = "movie", value = 2)
  case object Magazine extends LibraryItem(3, "magazine")
  case object CD       extends LibraryItem(4, name = "cd")
  // case object Newspaper extends LibraryItem(4, name = "newspaper") <-- will fail to compile because the value 4 is shared

  /*
  val five = 5
  case object Article extends LibraryItem(five, name = "article") <-- will fail to compile because the value is not a literal
  */

  val values = findValues

}

assert(LibraryItem.withValue(1) == LibraryItem.Book)

LibraryItem.withValue(10) // => java.util.NoSuchElementException:

If you want to allow aliases in your enumeration, i.e. multiple entries that share the same value, you can extend the enumeratum.values.AllowAlias trait:

import enumeratum.values._

sealed abstract class Judgement(val value: Int) extends IntEnumEntry with AllowAlias

object Judgement extends IntEnum[Judgement] {

  case object Good extends Judgement(1)
  case object OK extends Judgement(2)
  case object Meh extends Judgement(2)
  case object Bad extends Judgement(3)

  val values = findValues

}

Calling withValue with an aliased value will return one of the corresponding entries. Which one it returns is undefined:

assert(Judgement.withValue(2) == Judgement.OK || Judgement.withValue(2) == Judgement.Meh)

Restrictions

  • ValueEnums must have their value members implemented as literal values.

ScalaJS

In a ScalaJS project, add the following to build.sbt:

Maven Central

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum" % enumeratumVersion
)

As expected, usage is exactly the same as normal Scala.

Play Integration

Maven Central

The enumeratum-play project is published separately and gives you access to various tools to help you avoid boilerplate in your Play project.

SBT

For enumeratum with full Play support:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-play" % enumeratumPlayVersion
)

Dependencies and compatibility notes

  • As of version 1.8, enumeratum-play requires Scala 2.13+ and Play >= 3

Usage

PlayEnum

The included PlayEnum trait is probably going to be the most interesting as it includes a bunch of built-in implicits like Json formats, Path bindables, Query string bindables, and Form field support.

For example:

package enums

import enumeratum._

sealed trait Greeting extends EnumEntry

object Greeting extends PlayEnum[Greeting] {

  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

/*
  Then make sure to import your PlayEnums into your routes in your Build.scala
  or build.sbt so that you can use them in your routes file.

  `routesImport += "enums._"`
*/


// You can also use the String Interpolating Routing DSL:

import play.api.routing.sird._
import play.api.routing._
import play.api.mvc._
Router.from {
    case GET(p"/hello/${Greeting.fromPath(greeting)}") => Action {
      Results.Ok(s"$greeting")
    }
}

PlayValueEnums

There are IntPlayEnum, LongPlayEnum, and ShortPlayEnum traits for use with IntEnumEntry, LongEnumEntry, and ShortEnumEntry respectively that provide Play-specific implicits as with normal PlayEnum. For example:

import enumeratum.values._

sealed abstract class PlayLibraryItem(val value: Int, val name: String) extends IntEnumEntry

case object PlayLibraryItem extends IntPlayEnum[PlayLibraryItem] {

  // A good mix of named, unnamed, named + unordered args
  case object Book     extends PlayLibraryItem(value = 1, name = "book")
  case object Movie    extends PlayLibraryItem(name = "movie", value = 2)
  case object Magazine extends PlayLibraryItem(3, "magazine")
  case object CD       extends PlayLibraryItem(4, name = "cd")

  val values = findValues

}

import play.api.libs.json.{ JsNumber, JsString, Json => PlayJson }
PlayLibraryItem.values.foreach { item =>
    assert(PlayJson.toJson(item) == JsNumber(item.value))
}

PlayFormFieldEnum

PlayEnum extends the trait PlayFormFieldEnum wich offers formField for mapping within a play.api.data.Form object.

import play.api.data.Form
import play.api.data.Forms._

object GreetingForm {
  val form = Form(
    mapping(
      "name"     -> nonEmptyText,
      "greeting" -> Greeting.formField
    )(Data.apply)(Data.unapply)
  )

  case class Data(
    name: String,
    greeting: Greeting)
}

Another alternative (if for example your Enum can't extend PlayEnum or PlayFormFieldEnum) is to create an implicit Format and bring it into scope using Play's of, i.e.

import play.api.data.Form
import play.api.data.Forms._

object Formats {
  implicit val greetingFormat = enumeratum.Forms.format(Greeting)
}

object GreetingForm {
  import Formats._

  val form = Form(
      mapping(
        "name"     -> nonEmptyText,
        "greeting" -> of[Greeting]
      )(Data.apply)(Data.unapply)
    )

    case class Data(
      name: String,
      greeting: Greeting)

}

Play JSON

Maven Central

The enumeratum-play-json project is published separately and gives you access to Play's auto-generated boilerplate for JSON serialization in your Enum's.

SBT

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-play-json" % enumeratumPlayJsonVersion
)

Usage

PlayJsonEnum

There are also PlayInsensitiveJsonEnum, PlayLowercaseJsonEnum, and PlayUppercaseJsonEnum traits for use. For example:

import enumeratum.{ PlayJsonEnum, Enum, EnumEntry }

sealed trait Greeting extends EnumEntry

object Greeting extends Enum[Greeting] with PlayJsonEnum[Greeting] {

  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

PlayJsonValueEnum

There are IntPlayJsonValueEnum, LongPlayJsonValueEnum, and ShortPlayJsonValueEnum traits for use with IntEnumEntry, LongEnumEntry, and ShortEnumEntry respectively. For example:

import enumeratum.values._

sealed abstract class JsonDrinks(val value: Short, name: String) extends ShortEnumEntry

case object JsonDrinks extends ShortEnum[JsonDrinks] with ShortPlayJsonValueEnum[JsonDrinks] {

  case object OrangeJuice extends JsonDrinks(value = 1, name = "oj")
  case object AppleJuice  extends JsonDrinks(value = 2, name = "aj")
  case object Cola        extends JsonDrinks(value = 3, name = "cola")
  case object Beer        extends JsonDrinks(value = 4, name = "beer")

  val values = findValues

}

import play.api.libs.json.{ JsNumber, JsString, Json => PlayJson, JsSuccess }

// Use to deserialise numbers to enum members directly
JsonDrinks.values.foreach { drink =>
    assert(PlayJson.toJson(drink) == JsNumber(drink.value))
}
assert(PlayJson.fromJson[JsonDrinks](JsNumber(3)) == JsSuccess(JsonDrinks.Cola))
assert(PlayJson.fromJson[JsonDrinks](JsNumber(19)).isError)

Circe

Maven Central

SBT

To use enumeratum with Circe:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-circe" % enumeratumCirceVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-circe" % enumeratumCirceVersion
)

Usage

Enum

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with CirceEnum[ShirtSize] {

  case object Small  extends ShirtSize
  case object Medium extends ShirtSize
  case object Large  extends ShirtSize

  val values = findValues

}

import io.circe.Json
import io.circe.syntax._

ShirtSize.values.foreach { size =>
    assert(size.asJson == Json.fromString(size.entryName))
}

ValueEnum

import enumeratum.values._

sealed abstract class CirceLibraryItem(val value: Int, val name: String) extends IntEnumEntry

case object CirceLibraryItem extends IntEnum[CirceLibraryItem] with IntCirceEnum[CirceLibraryItem] {

  // A good mix of named, unnamed, named + unordered args
  case object Book     extends CirceLibraryItem(value = 1, name = "book")
  case object Movie    extends CirceLibraryItem(name = "movie", value = 2)
  case object Magazine extends CirceLibraryItem(3, "magazine")
  case object CD       extends CirceLibraryItem(4, name = "cd")

  val values = findValues

}

import io.circe.Json
import io.circe.syntax._

CirceLibraryItem.values.foreach { item =>
    assert(item.asJson == Json.fromInt(item.value))
}

ReactiveMongo BSON

Maven Central

The enumeratum-reactivemongo-bson project is published separately and gives you access to ReactiveMongo's auto-generated boilerplate for BSON serialization in your Enum's.

SBT

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-reactivemongo-bson" % enumeratumReactiveMongoVersion
)

Usage

ReactiveMongoBsonEnum

For example:

import enumeratum.{ ReactiveMongoBsonEnum, Enum, EnumEntry }

sealed trait Greeting extends EnumEntry

object Greeting extends Enum[Greeting] with ReactiveMongoBsonEnum[Greeting] {

  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

ReactiveMongoBsonValueEnum

There are IntReactiveMongoBsonValueEnum, LongReactiveMongoBsonValueEnum, and ShortReactiveMongoBsonValueEnum traits for use with IntEnumEntry, LongEnumEntry, and ShortEnumEntry respectively. For example:

import enumeratum.values._

sealed abstract class BsonDrinks(val value: Short, name: String) extends ShortEnumEntry

case object BsonDrinks extends ShortEnum[BsonDrinks] with ShortReactiveMongoBsonValueEnum[BsonDrinks] {

  case object OrangeJuice extends BsonDrinks(value = 1, name = "oj")
  case object AppleJuice  extends BsonDrinks(value = 2, name = "aj")
  case object Cola        extends BsonDrinks(value = 3, name = "cola")
  case object Beer        extends BsonDrinks(value = 4, name = "beer")

  val values = findValues

}

import reactivemongo.api.bson._

// Use to deserialise numbers to enum members directly
BsonDrinks.values.foreach { drink =>
  val writer = implicitly[BSONWriter[BsonDrinks]]

  assert(writer.write(drink) == BSONInteger(drink.value))
}

val reader = implicitly[BSONReader[BsonDrinks]]

assert(reader.read(BSONInteger(3)) == BsonDrinks.Cola)

Argonaut

Maven Central

SBT

To use enumeratum with Argonaut:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-argonaut" % enumeratumArgonautVersion
)

Usage

Enum

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

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)
}

Json4s

Maven Central

SBT

To use enumeratum with Json4s:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-json4s" % enumeratumJson4sVersion
)

Usage

Enum

import enumeratum._

sealed trait TrafficLight extends EnumEntry
object TrafficLight extends Enum[TrafficLight] /* nothing extra here */ {
  case object Red    extends TrafficLight
  case object Yellow extends TrafficLight
  case object Green  extends TrafficLight

  val values = findValues
}

import org.json4s.DefaultFormats

implicit val formats = DefaultFormats + Json4s.serializer(TrafficLight)

ValueEnum

import enumeratum.values._

sealed abstract class Device(val value: Short) extends ShortEnumEntry
case object Device
  extends ShortEnum[Device] /* nothing extra here */  {
  case object Phone   extends Device(1)
  case object Laptop  extends Device(2)
  case object Desktop extends Device(3)
  case object Tablet  extends Device(4)

  val values = findValues
}

import org.json4s.DefaultFormats

implicit val formats = DefaultFormats + Json4s.serializer(Device)

ScalaCheck

Maven Central

SBT

To use enumeratum with ScalaCheck:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-scalacheck" % enumeratumScalacheckVersion
)

Usage

Enum

Given the enum declared in the quick-start section, you can get an Arbitrary[Greeting] (to generate instances of Greeting) and a Cogen[Greeting] (to generate instances of Greeting => (A: Arbitrary)) by importing generators in the scope of your tests:

import enumeratum.scalacheck._

ValueEnum

Similarly, you can get Arbitrary and Cogen instances for every ValueEnum subtype by importing generators in the scope of your tests:

import enumeratum.values.scalacheck._

Quill

Maven Central

SBT

To use enumeratum with Quill:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-quill" % enumeratumQuillVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-quill" % enumeratumQuillVersion
)

Usage

Enum

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with QuillEnum[ShirtSize] {

  case object Small  extends ShirtSize
  case object Medium extends ShirtSize
  case object Large  extends ShirtSize

  val values = findValues

}

case class Shirt(size: ShirtSize)

import io.getquill._

lazy val ctx = new PostgresJdbcContext(SnakeCase, "ctx")
import ctx._

ctx.run(query[Shirt].insert(_.size -> lift(ShirtSize.Small: ShirtSize)))

ctx.run(query[Shirt]).foreach(println)
  • Note that a type ascription to the EnumEntry trait (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded EnumEntrys

ValueEnum

import enumeratum._

sealed abstract class ShirtSize(val value: Int) extends IntEnumEntry

case object ShirtSize extends IntEnum[ShirtSize] with IntQuillEnum[ShirtSize] {

  case object Small  extends ShirtSize(1)
  case object Medium extends ShirtSize(2)
  case object Large  extends ShirtSize(3)

  val values = findValues

}

case class Shirt(size: ShirtSize)

import io.getquill._

lazy val ctx = new PostgresJdbcContext(SnakeCase, "ctx")
import ctx._

ctx.run(query[Shirt].insert(_.size -> lift(ShirtSize.Small: ShirtSize)))

ctx.run(query[Shirt]).foreach(println)
  • Note that a type ascription to the ValueEnumEntry abstract class (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded ValueEnumEntrys
  • quill-cassandra currently does not support ShortEnum and ByteEnum (see getquill/quill#1009)
  • quill-orientdb currently does not support ByteEnum (see getquill/quill#1029)

Slick integration

Maven Central

Column Mappings

In order to use your enumeratum Enums in Slick tables as columns, you will need to construct instances of MappedColumnType and make them available where you define and query your slick tables. In order to more easily construct these instances, the enumeratum-slick integration provides a trait enumeratum.SlickEnumSupport. This trait provides a method mappedColumnTypeForEnum (and variants) for constructing a mapped column type for your enum. For example if you want to use Enum[Greeting] in your slick table, mix in SlickEnumSupport where you define your table.

trait GreetingRepository extends SlickEnumSupport {
  val profile: slick.jdbc.Profile
  implicit lazy val greetingMapper = mappedColumnTypeForEnum(Greeting)
  class GreetingTable(tag: Tag) extends Table[(String, Greeting)](tag, "greeting") {
    def id = column[String]("id", O.PrimaryKey)
    def greeting = column[Greeting]("greeting") // Maps to a varchar/text column

    def * = (id, greeting)
  }

ValueEnum Mappings

If you want to represent a ValueEnum by its value rather than its string name, simply mix in SlickValueEnumSupport and proceed mostly as above:

implicit lazy val libraryItemMapper = mappedColumnTypeForIntEnum(LibraryItem)
...
def item = column[LibraryItem]("LIBRARY_ITEM") // Maps to a numeric column

Common Mappers

An alternate approach which is useful when mappers need to be shared across repositories (perhaps for something common like a "Status" enum) is to define your mappers in a module on their own, then make use of them in your repositories:

trait CommonMappers extends SlickEnumSupport {
  val profile: Profile
  implicit lazy val statusMapper = mappedColumnTypeForEnum(Status)
  ...
}
trait UserRepository extends CommonMappers {
  val profile: Profile
  class UserTable(tag: Tag) extends Table[UserRow](tag, "user") {
    ...
    def status = column[Status]("status")
    ...
  }
}

Querying by enum column types

Note that because your enum values are singleton objects, you may get errors when you try to use them in Slick queries like the following:

.filter(_.trafficLight === TrafficLight.Red)

This is because TrafficLight.Red in the above example is inferred to be of its unique type (TrafficLight.Red) rather than TrafficLight, thus causing a failure to find your mapping. In order to fix this, simply assist the compiler by ascribing the type to be TrafficLight:

.filter(_.trafficLight === (TrafficLight.Red: TrafficLight))

A way around this if you find the type expansion offensive is to define val accessors for your enum entries that are typed as the parent type. You can do this inside your Enums companion object or more locally:

val red: TrafficLight = Red // Not red: TrafficLight.Red = Red
val yellow: TrafficLight = Yellow
val green: TrafficLight = Green
...
.filter(_.trafficLight === red)

Interpolated / Plain SQL integration

If you want to use slick interpolated SQL queries you can use the provided constructors to instantiate instances of GetResult[_] and SetParameter[_] for your enum:

import SlickEnumPlainSqlSupport._

Or mix it in...

trait Foo extends SlickEnumPlainSqlSupport {
  ...
}

Then define your instances:

implicit val greetingGetResult = getResultForEnum(Greeting)
implicit val greetingOptionGetResult = optionalGetResultForEnum(Greeting)
implicit val greetingSetParameter = setParameterForEnum(Greeting)
implicit val greetingOptionSetParameter = optionalSetParameterForEnum(Greeting)

Cats

Maven Central

SBT

To use enumeratum with Cats:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-cats" % enumeratumCatsVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-cats" % enumeratumCatsVersion
)

Usage

This enumeratum module is mostly useful for generic derivation - providing instances for Eq, Show and Hash. So if you have structures (for example case classes) which contain enum values, you get the instances for the enum itself "for free". But it can be useful for standalone usage as, providing type-safe comparison and hashing.

Enum

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with CatsEnum[ShirtSize] {

  case object Small   extends ShirtSize
  case object Medium  extends ShirtSize
  case object Large   extends ShirtSize

  val values = findValues

}

import cats.syntax.eq._
import cats.syntax.show._

val shirtSizeOne: ShirtSize = ...
val shirtSizeTwo: ShirtSize = ...

if(shirtSizeOne === shirtSizeTwo) { // note the third equals
    printf("We got the same size, its hash is: %i", implicitly[Hash[TrafficLight]].hash(shirtSizeOne))
} else {
    printf("Shirt sizes mismatch: %s =!= %s", shirtSizeOne.show, shirtSizeTwo.show)
}

ValueEnum

There are two implementations for ValueEnum:

  • CatsValueEnum provides the same functionality as CatsEnum (except Hash)
  • CatsOrderValueEnum provides the same functionality as CatsValueEnum plus an instance of cats.Order (due to Scala 2 trait limitations, it's an abstract class, check out CatsCustomOrderValueEnum if you need a trait)
import enumeratum.values._

sealed abstract class CatsPriority(val value: Int, val name: String) extends IntEnumEntry

case object CatsPriority extends IntEnum[CatsPriority] with CatsOrderValueEnum[Int, CatsPriority] {

  // A good mix of named, unnamed, named + unordered args
  case object Low         extends CatsPriority(value = 1, name = "low")
  case object Medium      extends CatsPriority(name = "medium", value = 2)
  case object High        extends CatsPriority(3, "high")
  case object SuperHigh   extends CatsPriority(4, name = "super_high")

  val values = findValues

}

import cats.instances.int._
import cats.instances.list._
import cats.syntax.order._
import cats.syntax.foldable._

val items: List[CatsPriority] = List(High, Low, SuperHigh)

items.maximumOption // Some(SuperHigh)

Inheritance-free usage

If you need instances, but hesitate to mix in the traits demonstrated above, you can get them using the provided methods in enumeratum.Cats and enumeratum.values.Cats - the second also provides more flexibility than the (opinionated) mix-in trait as it allows to pass a custom type class instance for the value type (methods names are prefixed with value).

Doobie

Maven Central

SBT

To use enumeratum with Doobie:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-doobie" % enumeratumDoobieVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-doobie" % enumeratumDoobieVersion
)

Usage

Enum

If you need to store enum values in text column of following table

CREATE TABLE clothes (
  shirt varchar(100)
)

you should use following code

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with DoobieEnum[ShirtSize] {

  case object Small  extends ShirtSize
  case object Medium extends ShirtSize
  case object Large  extends ShirtSize

  val values = findValues

}

case class Shirt(size: ShirtSize)

import doobie._
import doobie.implicits._
import doobie.util.ExecutionContexts
import cats.effect._

implicit val cs = IO.contextShift(ExecutionContexts.synchronous)

val xa = Transactor.fromDriverManager[IO](
  "org.postgresql.Driver",
  "jdbc:postgresql:world",
  "postgres",
  "",
  Blocker.liftExecutionContext(ExecutionContexts.synchronous)
)

sql"insert into clothes (shirt) values (${Shirt(ShirtSize.Small)})".update.run
  .transact(xa)
  .unsafeRunSync

sql"select shirt from clothes"
  .query[Shirt]
  .to[List]
  .transact(xa)
  .unsafeRunSync
  .take(5)
  .foreach(println)
  • Note that a type ascription to the EnumEntry trait (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded EnumEntrys

ValueEnum

import enumeratum.values.{ IntDoobieEnum, IntEnum, IntEnumEntry }

sealed abstract class ShirtSize(val value: Int) extends IntEnumEntry

case object ShirtSize extends IntEnum[ShirtSize] with IntDoobieEnum[ShirtSize] {

  case object Small  extends ShirtSize(1)
  case object Medium extends ShirtSize(2)
  case object Large  extends ShirtSize(3)

  val values = findValues

}

case class Shirt(size: ShirtSize)

import doobie._
import doobie.implicits._
import doobie.util.ExecutionContexts
import cats.effect._

implicit val cs = IO.contextShift(ExecutionContexts.synchronous)

val xa = Transactor.fromDriverManager[IO](
  "org.postgresql.Driver",
  "jdbc:postgresql:world",
  "postgres",
  "",
  Blocker.liftExecutionContext(ExecutionContexts.synchronous)
)

sql"insert into clothes (shirt) values (${Shirt(ShirtSize.Small)})".update.run
  .transact(xa)
  .unsafeRunSync

sql"select shirt from clothes"
  .query[Shirt]
  .to[List]
  .transact(xa)
  .unsafeRunSync
  .take(5)
  .foreach(println)
  • Note that a type ascription to the ValueEnumEntry abstract class (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded ValueEnumEntrys

Anorm

Anorm provides a module to support Enum as Column and parameters.

Benchmarking

Benchmarking is in the unpublished benchmarking project. It uses JMH and you can run them in the sbt console by issuing the following command from your command line:

sbt +benchmarking/'jmh:run -i 10 -wi 10 -f3 -t 1'

The above command will run JMH benchmarks against different versions of Scala. Leave off + to run against the main/latest supported version of Scala.

On my late 2013 MBP using Java8 on OSX El Capitan:

[info] Benchmark                                            Mode  Cnt     Score    Error  Units
[info] EnumBenchmarks.indexOf                               avgt   30    11.628 ±  0.190  ns/op
[info] EnumBenchmarks.withNameDoesNotExist                  avgt   30  1809.194 ± 33.113  ns/op
[info] EnumBenchmarks.withNameExists                        avgt   30    13.540 ±  0.374  ns/op
[info] EnumBenchmarks.withNameOptionDoesNotExist            avgt   30     5.999 ±  0.037  ns/op
[info] EnumBenchmarks.withNameOptionExists                  avgt   30     9.662 ±  0.232  ns/op
[info] StdLibEnumBenchmarks.withNameDoesNotExist            avgt   30  1921.690 ± 78.898  ns/op
[info] StdLibEnumBenchmarks.withNameExists                  avgt   30    56.517 ±  1.161  ns/op
[info] values.ValueEnumBenchmarks.withValueDoesNotExist     avgt   30  1950.291 ± 29.292  ns/op
[info] values.ValueEnumBenchmarks.withValueExists           avgt   30     4.009 ±  0.062  ns/op
[info] values.ValueEnumBenchmarks.withValueOptDoesNotExist  avgt   30     5.285 ±  0.063  ns/op
[info] values.ValueEnumBenchmarks.withValueOptExists        avgt   30     6.621 ±  0.084  ns/op

Discussion

Other than the methods that throw NoSuchElementExceptions, performance is in the 10ns range (taking into account JMH overhead of roughly 2-3ns), which is acceptable for almost all use-cases. PRs that promise to increase performance are expected to be demonstrably faster.

Also, Enumeratum's withName is faster than the standard library's Enumeration, by around 4x in the case where an entry exists with the given name. My guess is this is because Enumeratum doesn't use any synchronized calls or volatile annotations. It is also faster in the case where there is no corresponding name, but not by a significant amount, perhaps because the high cost of throwing an exception masks any benefits.

Publishing

Projects are published independently of each other.

JVM + ScalaJS projects should have an aggregate project to make it easy to publish them, e.g. for enumeratum-circe:

$ sbt "project circe-aggregate" +clean +publish-signed

Should publish all needed artefacts. Note that sbt circe-aggregate/publish-signed will not work (ScalaJS gets skipped).

Contributions

Issues and PRs are more than welcome.

  • For bug fixes, enhancements, version bumps etc: please feel free to send a PR or issue
  • For new integrations: these are generally bigger investments, and not all projects are a good fit to be maintained by me, so it would be a good idea to send an issue first to gauge interest and fit. If you feel it's a faster/better to have a concrete PR to discuss things with, by all means, feel free to go that route too.

More Repositories

1

frunk

Funktional generic type-level programming in Rust: HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid and friends.
Rust
1,185
star
2

diesel

Boilerplate-free, zero-overhead Tagless Final / typed-final / Finally Tagless DSLs in Scala
Scala
110
star
3

schwatcher

File-watching library for Scala. Built on Java 7's WatchService, RxScala and Akka actors.
Scala
104
star
4

slim-play

Slim Play app
Scala
76
star
5

rhodddoobie

My little sandbox for playing around with the FP + OOP + DDD combination, in particular using Rho, doobie, Docker, testing, etc in a project.
Scala
42
star
6

ansible-kafka-cluster

Zookeeper quorum + Kafka cluster on CentOS via Vagrant + Ansible
42
star
7

rusqbin

A server that stashes your requests for later retrieval so you can do end-to-end testing of code that makes HTTP calls. Available as a binary, a Docker image, and a library.
Rust
26
star
8

zipkin-futures

Zipkin tracing for Scala Futures and non-Futures (synchronous operations)
Scala
21
star
9

push_to_devices

Ruby-based ( Padrino / Sinatra ) server for pushing notifications to iOS and Android devices via Apple Push Notifications (APN) and Google Cloud Message (GCM) respectively.
Ruby
18
star
10

todddo-openapi-rs

Survey of the Rust web scene in mid-2019, covering async/await, DDD-esque structure, testing, mocking, OpenAPI, and Actix
Rust
17
star
11

freAST

Fast, simple Free Monads using ScalaMeta macro annotations. Port of Freasy-Monad.
Scala
14
star
12

sbt-opencv

Start using OpenCV in your JVM project in just 1 line, no separate compiling, installing OpenCV, or fussing with your system required.
Scala
13
star
13

sparkka-streams

Power a Spark Stream from anywhere in your Akka Stream Flow
Scala
12
star
14

metascraper-service

A completely non-blocking RESTful Play2 based API application that uses the Metascraper library to scrape URLs for metadata.
Scala
12
star
15

http4s-doobie-docker-scratchpad

Playing around with http4s + doobie + docker
Scala
10
star
16

ltsv-logger

Convenient and performant logging in LTSV for Scala
Scala
10
star
17

tasques

Background Tasks system backed by Elasticsearch with support for language agnostic Workers.
Go
9
star
18

scala-akka-cv-part1

Scala, Akka, Streams, JavaCV, OpenCV O MY! Part 1
Scala
7
star
19

finnhub-ws-rs

UI for finnhub.io live trades websocket API, written in Rust, compiled to WASM
Rust
7
star
20

scala-akka-cv-part2

Scala, Akka, Streams, JavaCV, OpenCV O MY! Part 2
Scala
7
star
21

chase-rs

Async + Sync file-following for people who care about file rotations and line numbers.
Rust
7
star
22

ansible-thumbor-centos

Ansible playbook for CentOS.
Shell
7
star
23

spray-servlet-scratchpad

Where I try to play around with Spray+Servlet
JavaScript
6
star
24

jhhi

Java Heap Histogram Ingest, written in Rust. Sends jmap heap histograms to Elasticsearch.
Rust
6
star
25

unless-when

`unless` and `when` macros for Scala 2.10+.
Scala
5
star
26

gol-rs

Conway's Game of Life in Rust + OpenGL
Rust
5
star
27

push_to_devices_rb

Ruby library for interfacing with Push to Devices server (https://github.com/lloydmeta/push_to_devices)
Ruby
5
star
28

centos-spark-cluster

CentOS w/ Zookeeper quorum and Spark cluster
5
star
29

hash_walker

A simple gem that allows you to traverse/walk a Hash (perhaps obtained from doing JSON::parse on a JSON string) according to a set of keys (also a hash), passing in a block to perform actions.
Ruby
4
star
30

seed-scala.g8

A basic skeleton Scala project
Scala
4
star
31

cogs

WIP client for MS Congitive services using async Hyper
Rust
4
star
32

actix-jwt-authc

JWT authentication middleware for Actix with support for JWT invalidation and custom Claims structs.
Rust
4
star
33

provide

@provide Scala annotation so you can annotate abstract method implementations safely instead of abusing `override`
Scala
4
star
34

aoc2021-rs

Advent of Code 2021 solutions in Rust
Rust
3
star
35

bloxi

A Blockchain implementation in Rust, following the "Learn Blockchains by Building One" tutorial.
Rust
3
star
36

scalameta.g8

sbt/Giter8 starter template for Scala.meta.
Scala
3
star
37

play-csv

CSV Path, Query, and Form binders for Play 2 framework apps.
Scala
3
star
38

redis-cleaner

A simple way of cleaning up a large number of Redis keys via [pattern matching](http://redis.io/commands/keys)
Ruby
2
star
39

Olivetti-PNG

Olivetti face set in PNG form
2
star
40

pseudo_encrypt-rs

A native Rust generic implementation of the pseudo_encrypt function from Psql
Rust
2
star
41

ctdi-play.g8

Compile-time DI Play template
Scala
2
star
42

sloword2vec-rs

A naive (read: slow) implementation of Word2Vec. Uses BLAS behind the scenes for speed.
Rust
2
star
43

todddo-openapi

Exploring DDD, dependency-management, testing, web in the world of Golang.
Go
2
star
44

hystrix-scala-scratchpad

Lloyd's Hystrix playground. Exploring Scala + Hystrix.
Scala
2
star
45

Risp

Reduced-Lisp in Scala Processor, Risp, is a simplified, type-safe, stack-safe Lisp written in Scala.
Scala
2
star
46

todddo-openapi-java

An exploration of modern Java API dev by building a reactive non-trivial app with DDD-esque structuring.
Java
2
star
47

fib-hs

Playing around with Haskell Stack toolbelt, asking questions
Haskell
1
star
48

elasticsearch-rs

Elasticsearch Rust Client
Rust
1
star
49

iBabble

Mobile client for Babble
Objective-C
1
star
50

aoc2020-rs

Advent of Code 2020 solutions in Rust
Rust
1
star
51

lloydmeta.github.io

HTML
1
star
52

bloop-with-gradle-and-silencer

Min repro
Kotlin
1
star
53

bloop-gradle-subprojects

Repro
Kotlin
1
star
54

bloop-on-gradle-with-its

A min repro of the bloop Gradle plugin not adding test frameworks to an integration test configuration
Java
1
star
55

mune

Scala
1
star
56

slim-play.g8

Giter8 template for a slim Play app
Scala
1
star
57

ngrams-enabler

A simple way of getting ngrams out of any given String object. Supports CJK (Chinese, Japanese, Korean) as well as alphabet based languages.
Ruby
1
star
58

ip-parsing-hs

Playing around with parsers in Haskell
Haskell
1
star
59

slim-play-activator

Activator template for a Slim Play project
Scala
1
star
60

thumbor-intro

HTML
1
star
61

gander

An HTML content extractor. Forked from a fork of Goose.
Scala
1
star
62

play-chatroom

Super simple websockets, Play framework chatroom w/ actors.
Scala
1
star
63

reculture-shields

Super simple shields for Re:Culture.
Scala
1
star
64

sbt-javacpp

Use JavaCPP and JavaCPP presets with ease. Base plugin for JavaCPP-related projects.
Scala
1
star
65

akanori-thrift

Thrift server version of akanori
Java
1
star
66

opa-gungnamstyle-scratchpad

OPA exploration
Open Policy Agent
1
star