Skip to content

Commit

Permalink
Custom validation of Configz instances via >=> (Kleisli) operator.
Browse files Browse the repository at this point in the history
  • Loading branch information
arosien committed Mar 10, 2013
1 parent ab10247 commit 8933705
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 16 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ val boolPath: Configz[Boolean] = "some.path.to.a.bool".path[Boolean]
val intPath: Configz[Int] = "some.path.to.an.int".path[Int]

// Get the values at the path, which may fail with a com.typesafe.config.ConfigException.
val boolProp: ValidationNEL[ConfigException, Boolean] = config.get(boolPath)
val intProp: ValidationNEL[ConfigException, Int] = config.get(intPath)
val boolProp: Settings[Boolean] = config.get(boolPath)
val intProp: Settings[Boolean, Int] = config.get(intPath)

// Configz is an applicative functor, so you can combine them (using scalaz operators):
val boolIntConfig: Configz[(Boolean, Int)] = (boolPath |@| intPath)(_ -> _)
val boolIntProp: ValidationNEL[ConfigException, (Boolean, Int)] = config.get(boolIntConfig)
// Note that Settings[A] is an alias for ValidationNEL[ConfigException, A].

// Configz is an applicative functor, so you can combine them (using scalaz operators like <*> or |@|):
val boolIntConfig: Configz[(Boolean, Int)] = (boolPath |@| intPath)(_ -> _)
val boolIntProp: Settings[(Boolean, Int)] = config.get(boolIntConfig)

// Configz paths can have custom validation using the >=> (Kleisli) operator:
val validatedIntPath = intPath >=> validate((_: Int) > 1000, "some.path.to.an.int must be > 1000")
```
4 changes: 3 additions & 1 deletion core/src/main/scala/Configz.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import Scalaz._
/** Reads settings from a [[com.typesafe.config.Config]]. */
sealed trait Configz[A] {
/** Read the settings from a config. */
def settings(config: Config): ValidationNEL[ConfigException, A]
def settings(config: Config): Settings[A]
}

object Configz {
implicit def configzToKleisli[A](configz: Configz[A]): Kleisli[Settings, Config, A] = kleisli(configz.settings)

implicit val ConfigzPure: Pure[Configz] = new Pure[Configz] {
def pure[A](a: => A): Configz[A] = new Configz[A] {
def settings(config: Config) =
Expand Down
21 changes: 18 additions & 3 deletions core/src/main/scala/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,36 @@ package net.rosien
* val boolProp: Settings[Boolean] = config.get(boolPath)
* val intProp: Settings[Int] = config.get(intPath)
*
* // Configz is an applicative functor, so you can combine them:
* // Configz is an applicative functor, so you can combine them (using scalaz operators like <*> or |@|):
* val boolIntConfig: Configz[(Boolean, Int)] = (boolPath |@| intPath)(_ -> _)
* val boolIntProp: Settings[(Boolean, Int)] = config.get(boolIntProp)
* val boolIntProp: Settings[(Boolean, Int)] = config.get(boolIntConfig)
*
* // Configz paths can have custom validation using the >=> (Kleisli) operator:
* val validatedIntPath = intPath >=> validate((_: Int) > 1000, "some.path.to.an.int must be > 1000")
* }}}
*/
package object configz {
import com.typesafe.config._
import scalaz._
import Scalaz._
import Validation.Monad._
import Configz._

type Settings[A] = ValidationNEL[ConfigException, A]

implicit val SettingsBind = implicitly[Bind[Settings]]

/** Validate a Configz path.
* @param f predicate function
* @param message failure message if f returns false
* @return validation function to be composed with Configz via >=> operator
*/
def validate[A](f: A => Boolean, message: String): A => Settings[A] = prop =>
if (f(prop)) prop.successNel else new ConfigException.Generic(message).failNel[A]

/** Additional methods on [[com.typesafe.config.Config]]. */
class ConfigOps(config: Config) {
def get[A](configz: Configz[A]): ValidationNEL[ConfigException, A] = configz.settings(config)
def get[A](configz: Kleisli[Settings, Config, A]): ValidationNEL[ConfigException, A] = configz(config)
}

implicit def configToConfigOps(config: Config): ConfigOps = new ConfigOps(config)
Expand Down
17 changes: 10 additions & 7 deletions core/src/test/scala/ConfigzSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import org.specs2.scalaz.ValidationMatchers

class ConfigzSpec extends Specification with ValidationMatchers {
def is = "Configz should" ^
"accumulate errors" ! configz().errors ^
"read settings" ! configz().settings ^
"accumulate errors" ! configz().errors ^
"read settings" ! configz().settings ^
"validate via kleisli" ! configz().validateKleisli ^
end

case class configz() {
Expand All @@ -16,6 +17,8 @@ class ConfigzSpec extends Specification with ValidationMatchers {
import Scalaz._

val config = ConfigFactory.load
val boolProp = "configz.bool".path[Boolean]
val intProp = "configz.int".path[Int]

def errors = {
val missing = "configz.asdf".path[String]
Expand All @@ -28,12 +31,12 @@ class ConfigzSpec extends Specification with ValidationMatchers {
}
}

def settings = {
val b = "configz.bool".path[Boolean]
val i = "configz.int".path[Int]
val bi = b <|*|> i
def settings = config.get(boolProp <|*|> intProp) must beSuccessful(true -> 1234)

config.get(bi) must beSuccessful(true -> 1234)
def validateKleisli = {
val validIntProp = validate((_: Int) < 1000, "configz.int must be < 1000")

(config.get(intProp >=> validIntProp) must beFailing)
}
}
}

0 comments on commit 8933705

Please sign in to comment.