configs is Scala wrapper for Typesafe config.
Add the following line to your build file:
libraryDependencies += "com.github.kxbmap" %% "configs" % "0.6.1"import com.typesafe.config.ConfigFactory
import configs.ConfigReaderResult type of get a value from config is configs.Result.
If get successfully, returns configs.Result.Success, if not configs.Result.Failure:
val config = ConfigFactory.parseString("foo = 42")val foo = ConfigReader[Int].read(config, "foo")
// foo: configs.Result[Int] = Success(value = 42)
foo.valueOrElse(0)
// res0: Int = 42
val missing = ConfigReader[Int].read(config, "missing")
// missing: configs.Result[Int] = Failure(
// error = ConfigError(
// head = Exceptional(
// throwable = com.typesafe.config.ConfigException$Missing: String: 1: No configuration setting found for key 'missing',
// paths = List("missing")
// ),
// tail = Vector()
// )
// )
missing.valueOrElse(0)
// res1: Int = 0Import configs.syntax._ provides extension methods for Config:
import configs.syntax._config.get[Int]("foo")
// res2: configs.Result[Int] = Success(value = 42)get[Option[A]] will return success with value None if path is not exists:
config.get[Option[Int]]("missing")
// res3: configs.Result[Option[Int]] = Success(value = None)
config.getOrElse("missing", 0) // Alias for config.get[Option[Int]]("missing").map(_.getOrElse(0))
// res4: configs.Result[Int] = Success(value = 0)You can get a case class value out of the box:
import scala.concurrent.duration.FiniteDuration
case class MyConfig(foo: String, bar: Int, baz: List[FiniteDuration])val config = ConfigFactory.parseString("""
my-config {
foo = My config value
bar = 123456
baz = [1h, 2m, 3s]
}
""")config.get[MyConfig]("my-config")
// res5: configs.Result[MyConfig] = Success(
// value = MyConfig(
// foo = "My config value",
// bar = 123456,
// baz = List(1 hour, 2 minutes, 3 seconds)
// )
// )If failed, Result accumulates error messages:
val config = ConfigFactory.parseString("""
my-config {
bar = 2147483648
baz = [aaa, bbb, ccc]
}
""")val result = config.get[MyConfig]("my-config")
// result: configs.Result[MyConfig] = Failure(
// error = ConfigError(
// head = Exceptional(
// throwable = com.typesafe.config.ConfigException$Missing: String: 2: No configuration setting found for key 'foo',
// paths = List("my-config", "foo")
// ),
// tail = Vector(
// Exceptional(
// throwable = com.typesafe.config.ConfigException$WrongType: String: 2: bar has type out-of-range value 2147483648 rather than int (32-bit integer),
// paths = List("my-config", "bar")
// ),
// Exceptional(
// throwable = com.typesafe.config.ConfigException$BadValue: String: 4: Invalid value at '0': No number in duration value 'aaa',
// paths = List("my-config", "baz", "0")
// ),
// Exceptional(
// throwable = com.typesafe.config.ConfigException$BadValue: String: 4: Invalid value at '1': No number in duration value 'bbb',
// paths = List("my-config", "baz", "1")
// ),
// Exceptional(
// throwable = com.typesafe.config.ConfigException$BadValue: String: 4: Invalid value at '2': No number in duration value 'ccc',
// paths = List("my-config", "baz", "2")
// )
// )
// )
// )
result.failed.foreach { error =>
error.messages.foreach(println)
}
// [my-config.foo] String: 2: No configuration setting found for key 'foo'
// [my-config.bar] String: 2: bar has type out-of-range value 2147483648 rather than int (32-bit integer)
// [my-config.baz.0] String: 4: Invalid value at '0': No number in duration value 'aaa'
// [my-config.baz.1] String: 4: Invalid value at '1': No number in duration value 'bbb'
// [my-config.baz.2] String: 4: Invalid value at '2': No number in duration value 'ccc'You can get a value without key using extract:
val config = ConfigFactory.parseString("""
foo = My config value
bar = 123456
baz = [1h, 2m, 3s]
""")config.extract[MyConfig]
// res7: configs.Result[MyConfig] = Success(
// value = MyConfig(
// foo = "My config value",
// bar = 123456,
// baz = List(1 hour, 2 minutes, 3 seconds)
// )
// )You may use the ~ operator to combine multiple results and apply a function with the results passed as arguments, this is useful when you want to construct a complex case class from several config extractors.
case class ServiceConfig(name: String, port: Int, hosts: List[String])
val config = ConfigFactory.parseString(
"""
|name = "foo"
|port = 9876
|hosts = ["localhost", "foo.com"]
""".stripMargin)(
config.get[String]("name") ~
config.get[Int]("port") ~
config.get[List[String]]("hosts")
)(ServiceConfig) // Alternatively (name, port, hosts) => ServerConfig(name, port, posts)
// res8: configs.Result[ServiceConfig] = Success(
// value = ServiceConfig(
// name = "foo",
// port = 9876,
// hosts = List("localhost", "foo.com")
// )
// )configs can get many type values from config.
It is provided by type class ConfigReader.
There are a number of built-in ConfigReader instances:
- Primitive/Wrapper types
Long,Int,Short,Byte,Double,Float,Char,Booleanjava.lang.{Long,Integer,Short,Byte,Double,Float,Character,Boolean}
- Big number types
BigInt,BigDecimaljava.math.{BigInteger,BigDecimal}
- String representation types
StringSymbol,java.util.{UUID,Locale}java.io.File,java.nio.file.Pathjava.net.{URI,InetAddress}
- Duration types
java.time.Durationscala.concurrent.duration.{Duration,FiniteDuration}
- Config types
com.typesafe.config.{Config,ConfigValue,ConfigList,ConfigObject,ConfigMemorySize}configs.Bytes
- Enum types
- Java
enumtypes - Scala
Enumerationtypes
- Java
- Collection types
F[A](usingCanBuildFrom[Nothing, A, F[A]], e.g.List[String],Seq[Int])M[S, A](usingCanBuildFrom[Nothing, (S, A), M[S, A]], e.g.Map[String, Int],TreeMap[UUID, Config])java.util.{List[A],Map[S, A],Set[A],Collection[A]},java.lang.Iterable[A]java.util.Properties
- Optional types
Option[A]java.util.{Optional[A],OptionalLong,OptionalInt,OptionalDouble}
- case classes
- ADTs (sealed trait + classes/objects). See ADTs support
- Java Beans. See Java Beans support
In this list, A means any type that is ConfigReader instance. And S means any type that is StringConverter instance.
If there is such an ADT:
sealed trait Tree
case class Branch(value: Int, left: Tree, right: Tree) extends Tree
case object Leaf extends TreeYou can get an ADT value from config:
val config = ConfigFactory.parseString("""
tree = {
value = 42
left = Leaf
right {
value = 123
left = Leaf
right = Leaf
}
}
""")config.get[Tree]("tree")
// res9: configs.Result[Tree] = Success(
// value = Branch(
// value = 42,
// left = Leaf,
// right = Branch(value = 123, left = Leaf, right = Leaf)
// )
// )If there is Java Beans class like the follows:
package com.example;
@lombok.Data
public class MyBean {
private int intValue;
private java.util.List<String> stringList;
private java.util.Map<java.util.Locale, java.time.Duration> localeToDuration;
}Then you define ConfigReader instance using deriveBean macro:
import com.example.MyBean
implicit val myBeanConfigReader: ConfigReader[MyBean] =
ConfigReader.deriveBean[MyBean]And then you can get Java Beans value:
val config = ConfigFactory.parseString("""
int-value = 42
string-list = [foo, bar, baz]
locale-to-duration {
ja_JP = 42ms
en_US = 123s
}
""")config.extract[MyBean]
// res11: configs.Result[MyBean] = Success(
// value = MyBean(intValue=42, stringList=[foo, bar, baz], localeToDuration={en_US=PT2M3S, ja_JP=PT0.042S})
// )Copyright 2013-2016 Tsukasa Kitachi
Apache License, Version 2.0