Figi is a small macro based utility for Scala 2.11 that allows you to easily create simple type-safe wrappers for configuration objects.
Scala
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
configrity/src
core/src/test/scala/ncreep/figi
macros/src/main/scala/ncreep/figi
project
LICENSE
README.md

README.md

Figi

Figi is a small macro based utility for Scala 2.11 that allows you to easily create simple type-safe wrappers for configuration objects.

Currently, Figi comes with built-in support for the Configrity library.

Getting Figi

Add these lines you to your SBT project:

resolvers += Resolver.bintrayRepo("ncreep", "maven")

libraryDependencies += "ncreep" %% "figi-core" % "0.1"

// optional, for use with the Configrity library
libraryDependencies += "ncreep" %% "figi-configrity" % "0.1"

Rationale

Say we want to read configuration data relying on some configuration library. The data contains:

user = figi
pass = 1234

We might model it as the following trait:

trait Setup {
  def user: String
  def pass: Int
}

And use that definition to pass around our code. At some point though, we will have to populate the members of a Setup instance:

val config = ...// read config from a file or something
val setup = new Setup {
  def user = config[String]("user")
  def pass = config[Int]("pass")
}

Figi saves you the trouble of manually writing this boilerplate, and generates the code using a def macro, like so:

val config = ...// read config from a file or something
val setup = Figi.makeConf[Setup](config)

And we can continue using our Setup instance as usual.

Features

  • Provide your own custom configuration traits, requires no dependencies on Figi
  • Code generation for vals and defs
  • Code generation ignores any non abstract members
  • Default values for missing keys
  • Chaining configuration traits (see example)
  • Easily extend support for your favorite configuration library using the typeclass pattern (example here)
  • Support for arbitrary converters provided by the configuration library
  • Support for Configrity out of the box

As Figi relies on macros to generate code, it may output some scary compilation error messages when something goes wrong; though a special effort was made to generate intelligible error messages where possible.

In any case, don't try being too smart when defining your trait.

Example

What follows is a more complete example based on the Configrity library. The full code can be found here.

// basic figi imports
import ncreep.figi._

// required for Configrity support
import ncreep.figi.configrity._
import org.streum.configrity._
import org.streum.configrity.converter.Extra._

// the traits specifying the settings we need
// except chained types, all types must have implicit converters in scope
trait Setup {
  val user: String
  val pass: Int
  def port(default: Int = 80): Int
  def missing: Int // using a val here will fail fast when generating a setup instance
  val dev: Deploy
  val prod: Deploy
  def upCaseUser = user.toUpperCase // will not be overridden by the macro
}

trait Deploy {
  val log: Boolean
  val root: File
}

// tells the compiler that the Deploy trait should be chained
implicit object deployIsConfChainer extends IsConfChainer[Deploy]

val base = Configuration(Map("user" -> "figi", "pass" -> "1234"))
val dev = Configuration(Map("log" -> "true", "root" -> "/dev/root/"))
val prod = Configuration(Map("log" -> "false", "root" -> "/prod/root/"))

val cnf = base.attach("dev", dev).attach("prod", prod)

// populating the members of a Setup instance from cnf
val setup = Figi.makeConf[Setup](cnf)

setup.user mustEqual "figi"
setup.pass mustEqual 1234
setup.port() mustEqual 80
setup.port(8080) mustEqual 8080
setup.missing must throwA[NoSuchElementException]
setup.dev.log mustEqual true
setup.dev.root mustEqual new File("/dev/root")
setup.prod.log mustEqual false
setup.prod.root mustEqual new File("/prod/root")
setup.upCaseUser mustEqual "FIGI"