Skip to content

Commit

Permalink
Merge c258d9f into 721603b
Browse files Browse the repository at this point in the history
  • Loading branch information
melrief committed Mar 22, 2017
2 parents 721603b + c258d9f commit f857f1e
Show file tree
Hide file tree
Showing 20 changed files with 913 additions and 1,016 deletions.
14 changes: 9 additions & 5 deletions core/src/main/scala/pureconfig/BasicConverters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,23 @@ trait UriAndPathConverters extends ConvertHelpers {
trait JavaTimeConverters extends ConvertHelpers {

implicit val instantConfigConvert: ConfigConvert[Instant] =
fromNonEmptyStringReader[Instant](catchReadError(Instant.parse))
fromNonEmptyStringConvert[Instant](catchReadError(Instant.parse), _.toString)

implicit val zoneOffsetConfigConvert: ConfigConvert[ZoneOffset] =
fromNonEmptyStringReader[ZoneOffset](catchReadError(ZoneOffset.of))
fromNonEmptyStringConvert[ZoneOffset](catchReadError(ZoneOffset.of), _.toString)

implicit val zoneIdConfigConvert: ConfigConvert[ZoneId] =
fromNonEmptyStringReader[ZoneId](catchReadError(ZoneId.of))
fromNonEmptyStringConvert[ZoneId](catchReadError(ZoneId.of), _.toString)

implicit val periodConfigConvert: ConfigConvert[Period] =
fromNonEmptyStringReader[Period](catchReadError(Period.parse))
fromNonEmptyStringConvert[Period](catchReadError(Period.parse), _.toString)

// see documentation for [[java.time.Year.parse]]
private def yearToString(year: Year): String =
if (year.getValue > 9999) "+" + year else year.toString

implicit val yearConfigConvert: ConfigConvert[Year] =
fromNonEmptyStringReader[Year](catchReadError(Year.parse))
fromNonEmptyStringConvert[Year](catchReadError(Year.parse), yearToString)
}

/**
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/pureconfig/DerivedConverters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ trait DerivedConverters extends ConvertHelpers {

override def to(t: Option[T]): ConfigValue = t match {
case Some(v) => conv.value.to(v)
case None => ConfigValueFactory.fromMap(Map().asJava)
case None => ConfigValueFactory.fromAnyRef(null)
}

def toOption(t: Option[T]): Option[ConfigValue] = t.map(conv.value.to)
Expand Down
6 changes: 5 additions & 1 deletion core/src/test/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
d: 0.0
i: 0
s: "default"
s: "default"

foo {
f: 3.0
}
6 changes: 6 additions & 0 deletions core/src/test/scala/pureconfig/AnimalConfig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package pureconfig

sealed trait AnimalConfig
case class DogConfig(age: Int) extends AnimalConfig
case class CatConfig(age: Int) extends AnimalConfig
case class BirdConfig(canFly: Boolean) extends AnimalConfig
125 changes: 125 additions & 0 deletions core/src/test/scala/pureconfig/ApiSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package pureconfig

import java.nio.file.Path
import java.util.concurrent.TimeUnit

import com.typesafe.config.ConfigFactory
import pureconfig.PathUtils._

import scala.concurrent.duration.FiniteDuration

class ApiSuite extends BaseSuite {

behavior of "pureconfig"

it should "loadConfig from reference.conf" in {
case class Conf(d: Double, i: Int, s: String)
loadConfig[Conf] shouldBe Right(Conf(0D, 0, "default"))
}

it should "loadConfig from reference.conf with a namespace" in {
case class Conf(f: Float)
loadConfig[Conf](namespace = "foo") shouldBe Right(Conf(3.0F))
}

it should "loadConfig from a Typesafe Config" in {
case class Conf(d: Double, i: Int)
val conf = ConfigFactory.parseString("{ d: 0.5, i: 10 }")
loadConfig[Conf](conf = conf) shouldBe Right(Conf(0.5D, 10))
}

it should "loadConfig from a Typesafe Config with a namespace" in {
case class Conf(f: Float)
val conf = ConfigFactory.parseString("foo.bar { f: 1.0 }")
loadConfig[Conf](conf = conf, namespace = "foo.bar") shouldBe Right(Conf(1.0F))
}

it should "loadConfig from a configuration file" in {
case class Conf(s: String, b: Boolean)
val path = createTempFile("""{ b: true, s: "str" }""")
loadConfig[Conf](path = path) shouldBe Right(Conf("str", true))
}

it should "loadConfig from a configuration file with a namespace" in {
case class Conf(s: String, b: Boolean)
val path = createTempFile("""foo.bar { b: true, s: "str" }""")
loadConfig[Conf](path = path, namespace = "foo.bar") shouldBe Right(Conf("str", true))
}

it should "be able to load a realistic configuration file" in {
case class DriverConf(cores: Int, maxResultSize: String, memory: String)
case class ExecutorConf(memory: String, extraJavaOptions: String)
case class SparkAppConf(name: String)
case class SparkLocalConf(dir: String)
case class SparkNetwork(timeout: FiniteDuration)
case class SparkConf(master: String, app: SparkAppConf, local: SparkLocalConf, driver: DriverConf, executor: ExecutorConf, extraListeners: Seq[String], network: SparkNetwork)
case class SparkRootConf(spark: SparkConf)
val configFile = createTempFile(
"""spark {
| app.name="myApp"
| master="local[*]"
| driver {
| maxResultSize="2g"
| memory="1g"
| cores="10"
| }
| executor {
| memory="2g"
| extraJavaOptions=""
| }
| extraListeners=[]
| local.dir="/tmp/"
| network.timeout=45s
|}
|// unused configuration
|akka.loggers = ["akka.event.Logging$DefaultLogger"]""".stripMargin)

implicit def productHint[T] = ProductHint[T](ConfigFieldMapping(CamelCase, CamelCase))
val configOrError = loadConfig[SparkRootConf](configFile)

val config = configOrError match {
case Left(f) => fail(f.toString)
case Right(c) => c
}

config.spark.executor.extraJavaOptions should be("")
config.spark.driver.maxResultSize should be("2g")
config.spark.extraListeners should be(Seq.empty[String])
config.spark.app.name should be("myApp")
config.spark.driver.memory should be("1g")
config.spark.driver.cores should be(10)
config.spark.master should be("local[*]")
config.spark.executor.memory should be("2g")
config.spark.local.dir should be("/tmp/")
config.spark.network.timeout should be(FiniteDuration(45, TimeUnit.SECONDS))
}

"loadConfigFromFiles" should "load a complete configuration from a single file" in {
case class Conf(b: Boolean, d: Double)
val files = listResourcesFromNames("/conf/loadConfigFromFiles/priority2.conf")
loadConfigFromFiles[Conf](files) shouldBe Right(Conf(false, 0.001D))
}

"loadConfigWithFallback" should "fallback if no config keys are found" in {
case class Conf(f: Float, o: Option[Int], d: Double)
val priority1Conf = ConfigFactory.load("conf/loadConfigFromFiles/priority1.conf")
// first wo are in priority1.conf, the d is in reference.conf
loadConfigWithFallback[Conf](priority1Conf) shouldBe Right(Conf(0.99F, None, 0.0))
}

it should "fill in missing values from the lower priority files" in {
case class Conf(f: Float)
val files = listResourcesFromNames("/conf/loadConfigFromFiles/priority1.conf", "/conf/loadConfigFromFiles/priority2.conf")
loadConfigFromFiles[Conf](files) shouldBe Right(Conf(0.99F))
}

it should "complain if the list of files is empty" in {
case class Conf(f: Float)
val files = Set.empty[Path]
loadConfigFromFiles[Conf](files) shouldBe a[Left[_, _]]
}
}
11 changes: 11 additions & 0 deletions core/src/test/scala/pureconfig/BaseSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pureconfig

import org.scalatest.{ EitherValues, FlatSpec, Matchers }
import org.scalatest.prop.GeneratorDrivenPropertyChecks

class BaseSuite
extends FlatSpec
with ConfigConvertChecks
with Matchers
with EitherValues
with GeneratorDrivenPropertyChecks
119 changes: 119 additions & 0 deletions core/src/test/scala/pureconfig/BasicConvertersSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package pureconfig

import java.net.{ URI, URL }
import java.nio.file.Path
import java.time._
import java.util.UUID

import com.typesafe.config._
import pureconfig.arbitrary._
import pureconfig.data.Percentage
import pureconfig.error.{ CannotConvert, EmptyStringFound }

import scala.collection.JavaConverters._
import scala.collection.immutable
import scala.concurrent.duration.{ Duration, FiniteDuration }

class BasicConvertersSuite extends BaseSuite {

behavior of "ConfigConvert"

checkArbitrary[Duration]
checkFailure[Duration, EmptyStringFound](ConfigValueFactory.fromAnyRef(""))
checkFailure[Duration, CannotConvert](ConfigValueFactory.fromIterable(List(1).asJava))

checkArbitrary[FiniteDuration]
checkFailure[FiniteDuration, EmptyStringFound](ConfigValueFactory.fromAnyRef(""))
checkFailure[FiniteDuration, CannotConvert](
ConfigValueFactory.fromIterable(List(1).asJava),
ConfigConvert[Duration].to(Duration.MinusInf),
ConfigConvert[Duration].to(Duration.Inf))

checkArbitrary[Instant]

checkArbitrary[ZoneOffset]

checkArbitrary[ZoneId]

checkArbitrary[Period]

checkArbitrary[Year]

checkArbitrary[String]

checkArbitrary[Boolean]

checkArbitrary[Double]
checkArbitrary2[Double, Percentage](_.toDoubleFraction)
checkFailure[Double, EmptyStringFound](ConfigValueFactory.fromAnyRef(""))
checkFailure[Double, CannotConvert](ConfigValueFactory.fromIterable(List(1, 2, 3, 4).asJava))

checkArbitrary[Float]
checkArbitrary2[Float, Percentage](_.toFloatFraction)

checkArbitrary[Int]

checkArbitrary[Long]

checkArbitrary[Short]

checkArbitrary[UUID]

checkArbitrary[Path]

checkArbitrary[immutable.HashSet[String]]

checkArbitrary[immutable.List[Float]]
check[immutable.List[Int]](
// order of keys maintained
(List(2, 3, 1), ConfigValueFactory.fromMap(Map("2" -> 1, "0" -> 2, "1" -> 3).asJava)),
(List(4, 2), ConfigValueFactory.fromMap(Map("3" -> 2, "1" -> 4).asJava)))
checkFailure[immutable.List[Int], CannotConvert](
ConfigValueFactory.fromMap(Map("1" -> 1, "a" -> 2).asJava))

checkArbitrary[immutable.ListSet[Int]]

checkArbitrary[immutable.Map[String, Int]]
checkFailure[immutable.Map[String, Int], CannotConvert](
ConfigFactory.parseString("conf.a=1").root(), // nested map should fail
ConfigFactory.parseString("{ a=b }").root()) // wrong value type should fail

checkArbitrary[immutable.Queue[Boolean]]

checkArbitrary[immutable.Set[Double]]
check[immutable.Set[Int]](
(Set(4, 5, 6), ConfigValueFactory.fromMap(Map("1" -> 4, "2" -> 5, "3" -> 6).asJava)))

checkArbitrary[immutable.Stream[String]]

checkArbitrary[immutable.TreeSet[Int]]

checkArbitrary[immutable.Vector[Short]]

checkArbitrary[Option[Int]]

check[URL](
new URL("http://host/path?with=query&param") -> ConfigValueFactory.fromAnyRef("http://host/path?with=query&param"))

check[URI](
new URI("http://host/path?with=query&param") -> ConfigValueFactory.fromAnyRef("http://host/path?with=query&param"))

check[ConfigList](
ConfigValueFactory.fromIterable(List().asJava) -> ConfigValueFactory.fromIterable(List().asJava),
ConfigValueFactory.fromIterable(List(1, 2, 3).asJava) -> ConfigValueFactory.fromAnyRef(List(1, 2, 3).asJava))

check[ConfigValue](
ConfigValueFactory.fromAnyRef(4) -> ConfigValueFactory.fromAnyRef(4),
ConfigValueFactory.fromAnyRef("str") -> ConfigValueFactory.fromAnyRef("str"),
ConfigValueFactory.fromAnyRef(List(1, 2, 3).asJava) -> ConfigValueFactory.fromAnyRef(List(1, 2, 3).asJava))

{
val conf = ConfigFactory.parseString("""{ v1 = 3, v2 = 4 }""".stripMargin)

check[ConfigObject](
ConfigValueFactory.fromMap(Map("v1" -> 3, "v2" -> 4).asJava) -> conf.root().asInstanceOf[ConfigValue])

check[Config](
conf -> conf.root().asInstanceOf[ConfigValue])
}
}
Loading

0 comments on commit f857f1e

Please sign in to comment.