From b041664d2a51de9c8af8ef70ece2f33edb12f366 Mon Sep 17 00:00:00 2001 From: Hugh Simpson Date: Mon, 26 Jul 2021 15:57:00 +0100 Subject: [PATCH 1/3] some initial cross-compilation stuff --- build.sbt | 44 +++++++------ .../ficus/readers/ArbitraryTypeReader.scala | 2 - .../ficus/readers/EnumerationReader.scala | 0 .../ceedubs/ficus/util/ReflectionUtils.scala | 0 .../ficus/readers/ArbitraryTypeReader.scala | 7 +++ .../ficus/readers/CollectionReaders.scala | 7 +++ .../ficus/readers/EnumerationReader.scala | 7 +++ .../ceedubs/ficus/util/ReflectionUtils.scala | 61 +++++++++++++++++++ .../net/ceedubs/ficus/readers/Generated.scala | 3 + .../net/ceedubs/ficus/ConfigSerializer.scala | 12 ++-- .../net/ceedubs/ficus/FicusConfigSpec.scala | 6 +- .../ficus/readers/AnyValReadersSpec.scala | 14 ++--- .../readers/ArbitraryTypeReaderSpec.scala | 17 +++--- .../ficus/readers/BigNumberReadersSpec.scala | 20 +++--- .../ficus/readers/CaseClassReadersSpec.scala | 17 +++--- .../ficus/readers/CollectionReadersSpec.scala | 4 +- .../ficus/readers/ConfigReaderSpec.scala | 10 +-- .../ficus/readers/ConfigValueReaderSpec.scala | 12 ++-- .../ficus/readers/DurationReadersSpec.scala | 10 +-- .../ficus/readers/EitherReadersSpec.scala | 12 ++-- .../ficus/readers/HyphenNameMapperSpec.scala | 4 +- .../ficus/readers/OptionReadersSpec.scala | 2 +- .../ficus/readers/StringReaderSpec.scala | 2 +- .../ficus/readers/SymbolReaderSpec.scala | 2 +- .../ceedubs/ficus/readers/TryReaderSpec.scala | 4 +- .../ficus/readers/ValueReaderSpec.scala | 2 +- 26 files changed, 188 insertions(+), 93 deletions(-) rename src/main/{scala => scala-2}/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala (98%) rename src/main/{scala => scala-2}/net/ceedubs/ficus/readers/EnumerationReader.scala (100%) rename src/main/{scala => scala-2}/net/ceedubs/ficus/util/ReflectionUtils.scala (100%) create mode 100644 src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala create mode 100644 src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala create mode 100644 src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala create mode 100644 src/main/scala-3/net/ceedubs/ficus/util/ReflectionUtils.scala create mode 100644 src/main/scala/net/ceedubs/ficus/readers/Generated.scala diff --git a/build.sbt b/build.sbt index 02c3212..76e4312 100644 --- a/build.sbt +++ b/build.sbt @@ -29,7 +29,7 @@ ThisBuild / githubWorkflowUseSbtThinClient := false ThisBuild / githubWorkflowPublishTargetBranches := Seq() -ThisBuild / crossScalaVersions := Seq("2.10.7", "2.11.12", "2.13.6", "2.12.14") +ThisBuild / crossScalaVersions := Seq("2.10.7", "2.11.12", "2.13.6", "3.0.1", "2.12.14") ThisBuild / scalaVersion := (ThisBuild / crossScalaVersions).value.last // Coveralls doesn't really work with Scala 2.10.7 so we are disabling it for CI @@ -65,34 +65,44 @@ lazy val root = project "-Xlint:deprecation" ), Compile / unmanagedSourceDirectories ++= { - (Compile / unmanagedSourceDirectories).value.map { dir => + (Compile / unmanagedSourceDirectories).value.flatMap { dir => CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 13)) => file(dir.getPath ++ "-2.13+") - case _ => file(dir.getPath ++ "-2.13-") + case Some((2, 13)) => Seq(file(dir.getPath ++ "-2.13+")) + case Some((2, _)) => Seq(file(dir.getPath ++ "-2.13-")) + case _ => Nil } } }, Test / unmanagedSourceDirectories ++= { - (Test / unmanagedSourceDirectories).value.map { dir => + (Test / unmanagedSourceDirectories).value.flatMap { dir => CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 13)) => file(dir.getPath ++ "-2.13+") - case _ => file(dir.getPath ++ "-2.13-") + case Some((2, 13)) => Seq(file(dir.getPath ++ "-2.13+")) + case Some((2, _)) => Seq(file(dir.getPath ++ "-2.13-")) + case _ => Nil } } }, libraryDependencies ++= - (if (scalaVersion.value.startsWith("2.10")) - Seq("org.specs2" %% "specs2-core" % "3.10.0" % Test, "org.specs2" %% "specs2-scalacheck" % "3.10.0" % Test) - else - Seq("org.specs2" %% "specs2-core" % "4.8.3" % Test, "org.specs2" %% "specs2-scalacheck" % "4.8.3" % Test)) ++ + (if (scalaVersion.value.startsWith("2.")) { + val specs2Version = if (scalaVersion.value.startsWith("2.10")) "3.10.0" else "4.8.3" + Seq( + "org.specs2" %% "specs2-core" % specs2Version % Test, + "org.specs2" %% "specs2-scalacheck" % specs2Version % Test, + "com.chuusai" %% "shapeless" % "2.3.3" % Test, + "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, + "org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided + ) + } else + Seq( + "org.specs2" %% "specs2-core" % "5.0.0-ALPHA-03" % Test, + "org.specs2" %% "specs2-scalacheck" % "5.0.0-ALPHA-03" % Test, + "org.typelevel" %% "shapeless3-typeable" % "3.0.2" % Test + )) ++ Seq( - "org.scalacheck" %% "scalacheck" % "1.14.1" % Test, - "com.chuusai" %% "shapeless" % "2.3.3" % Test, - "com.typesafe" % "config" % "1.3.4", - "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, - "org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided + "org.scalacheck" %% "scalacheck" % "1.15.4" % Test, + "com.typesafe" % "config" % "1.3.4" ) ++ - (if (!scalaVersion.value.startsWith("2.13")) + (if (scalaVersion.value.startsWith("2.") && !scalaVersion.value.startsWith("2.13")) Seq( compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full), "org.typelevel" %% "macro-compat" % "1.1.1" diff --git a/src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala b/src/main/scala-2/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala similarity index 98% rename from src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala rename to src/main/scala-2/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala index 47033fa..9fdefe6 100644 --- a/src/main/scala/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala +++ b/src/main/scala-2/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala @@ -8,8 +8,6 @@ import scala.language.experimental.macros import scala.reflect.internal.{Definitions, StdNames, SymbolTable} import scala.reflect.macros.blackbox -case class Generated[+A](value: A) extends AnyVal - trait ArbitraryTypeReader { implicit def arbitraryTypeValueReader[T]: Generated[ValueReader[T]] = macro ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T] diff --git a/src/main/scala/net/ceedubs/ficus/readers/EnumerationReader.scala b/src/main/scala-2/net/ceedubs/ficus/readers/EnumerationReader.scala similarity index 100% rename from src/main/scala/net/ceedubs/ficus/readers/EnumerationReader.scala rename to src/main/scala-2/net/ceedubs/ficus/readers/EnumerationReader.scala diff --git a/src/main/scala/net/ceedubs/ficus/util/ReflectionUtils.scala b/src/main/scala-2/net/ceedubs/ficus/util/ReflectionUtils.scala similarity index 100% rename from src/main/scala/net/ceedubs/ficus/util/ReflectionUtils.scala rename to src/main/scala-2/net/ceedubs/ficus/util/ReflectionUtils.scala diff --git a/src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala b/src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala new file mode 100644 index 0000000..fb274ea --- /dev/null +++ b/src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala @@ -0,0 +1,7 @@ +package net.ceedubs.ficus.readers + +trait ArbitraryTypeReader { + implicit def arbitraryTypeValueReader[T]: Generated[ValueReader[T]] = ??? +} + +object ArbitraryTypeReader extends ArbitraryTypeReader \ No newline at end of file diff --git a/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala b/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala new file mode 100644 index 0000000..96ccee2 --- /dev/null +++ b/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala @@ -0,0 +1,7 @@ +package net.ceedubs.ficus.readers + +trait CollectionReaders { + +} + +object CollectionReaders extends CollectionReaders \ No newline at end of file diff --git a/src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala b/src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala new file mode 100644 index 0000000..9406638 --- /dev/null +++ b/src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala @@ -0,0 +1,7 @@ +package net.ceedubs.ficus.readers + +trait EnumerationReader { + +} + +object EnumerationReader extends EnumerationReader \ No newline at end of file diff --git a/src/main/scala-3/net/ceedubs/ficus/util/ReflectionUtils.scala b/src/main/scala-3/net/ceedubs/ficus/util/ReflectionUtils.scala new file mode 100644 index 0000000..11e6e0b --- /dev/null +++ b/src/main/scala-3/net/ceedubs/ficus/util/ReflectionUtils.scala @@ -0,0 +1,61 @@ +//package net.ceedubs.ficus.util +// +//import macrocompat.bundle +//import scala.reflect.macros.blackbox +// +//@bundle +//trait ReflectionUtils { +// val c: blackbox.Context +// +// import c.universe._ +// +// def instantiationMethod[T: c.WeakTypeTag](fail: String => Nothing): c.universe.MethodSymbol = { +// +// val returnType = c.weakTypeOf[T] +// +// val returnTypeTypeArgs = returnType match { +// case TypeRef(_, _, args) => args +// case _ => Nil +// } +// +// if (returnTypeTypeArgs.nonEmpty) +// fail( +// s"value readers cannot be auto-generated for types with type parameters. Consider defining your own ValueReader[$returnType]" +// ) +// +// val companionSymbol = returnType.typeSymbol.companion match { +// case NoSymbol => None +// case x => Some(x) +// } +// +// val applyMethods = companionSymbol.toList.flatMap(_.typeSignatureIn(returnType).members collect { +// case m: MethodSymbol if m.name.decodedName.toString == "apply" && m.returnType <:< returnType => m +// }) +// +// val applyMethod = applyMethods match { +// case Nil => None +// case (head :: Nil) => Some(head) +// case _ => fail(s"its companion object has multiple apply methods that return type $returnType") +// } +// +// applyMethod getOrElse { +// val primaryConstructor = returnType.decl(termNames.CONSTRUCTOR) match { +// case t: TermSymbol => +// val constructors = t.alternatives collect { +// case m: MethodSymbol if m.isConstructor => m +// } +// val primaryScalaConstructor = constructors.find(m => m.isPrimaryConstructor && !m.isJava) +// primaryScalaConstructor orElse { +// if (constructors.length == 1) constructors.headOption else None +// } +// case _ => None +// } +// primaryConstructor getOrElse { +// fail( +// s"it has no apply method in a companion object that returns type $returnType, and it doesn't have a primary constructor" +// ) +// } +// } +// } +// +//} diff --git a/src/main/scala/net/ceedubs/ficus/readers/Generated.scala b/src/main/scala/net/ceedubs/ficus/readers/Generated.scala new file mode 100644 index 0000000..ed7223f --- /dev/null +++ b/src/main/scala/net/ceedubs/ficus/readers/Generated.scala @@ -0,0 +1,3 @@ +package net.ceedubs.ficus.readers + +case class Generated[+A](value: A) extends AnyVal diff --git a/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala b/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala index c4f47fa..7e36580 100644 --- a/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala +++ b/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala @@ -20,11 +20,11 @@ object ConfigSerializer { s"[${elements.mkString(", ")}]" } - implicit val stringSerializer = apply[String](ConfigUtil.quoteString) - implicit val booleanSerializer = fromToString[Boolean] - implicit val intSerializer = fromToString[Int] - implicit val longSerializer = fromToString[Long] - implicit val doubleSerializer = fromToString[Double] + implicit val stringSerializer: ConfigSerializer[String] = apply[String](ConfigUtil.quoteString) + implicit val booleanSerializer: ConfigSerializer[Boolean] = fromToString[Boolean] + implicit val intSerializer: ConfigSerializer[Int] = fromToString[Int] + implicit val longSerializer: ConfigSerializer[Long] = fromToString[Long] + implicit val doubleSerializer: ConfigSerializer[Double] = fromToString[Double] implicit def listSerializer[A: ConfigSerializer]: ConfigSerializer[List[A]] = apply[List[A]](serializeIterable) implicit def serializerForSets[A: ConfigSerializer]: ConfigSerializer[Set[A]] = apply[Set[A]](serializeIterable) @@ -36,7 +36,7 @@ object ConfigSerializer { } def iterableSerializer[A: ConfigSerializer]: ConfigSerializer[Iterable[A]] = apply[Iterable[A]](serializeIterable) - implicit def stringKeyMapSerializer[A](implicit valueSerializer: ConfigSerializer[A]) = + implicit def stringKeyMapSerializer[A](implicit valueSerializer: ConfigSerializer[A]): ConfigSerializer[Map[String, A]] = new ConfigSerializer[Map[String, A]] { def serialize(map: Map[String, A]): String = { val lines = map.toIterable.map( diff --git a/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala b/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala index 1d3467d..8d37706 100644 --- a/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala @@ -22,12 +22,12 @@ class FicusConfigSpec extends Spec { cfg.as[Boolean]("myValue") must beTrue } - def readAValue = prop { b: Boolean => + def readAValue = prop { (b: Boolean) => val cfg = ConfigFactory.parseString(s"myValue = $b") cfg.as[Boolean]("myValue") must beEqualTo(b) } - def getAsSome = prop { b: Boolean => + def getAsSome = prop { (b: Boolean) => val cfg = ConfigFactory.parseString(s"myValue = $b") cfg.getAs[Boolean]("myValue") must beSome(b) } @@ -60,7 +60,7 @@ class FicusConfigSpec extends Spec { cfg.getOrElse("myValue", default) must beEqualTo(124.toByte) } - def acceptAConfigKey = prop { b: Boolean => + def acceptAConfigKey = prop { (b: Boolean) => val cfg = ConfigFactory.parseString(s"myValue = $b") val key: ConfigKey[Boolean] = SimpleConfigKey("myValue") cfg(key) must beEqualTo(b) diff --git a/src/test/scala/net/ceedubs/ficus/readers/AnyValReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/AnyValReadersSpec.scala index 20208d5..7244ffe 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/AnyValReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/AnyValReadersSpec.scala @@ -21,39 +21,39 @@ class AnyValReadersSpec extends Spec with AnyValReaders { read an int as a double $readIntAsDouble """ - def readBoolean = prop { b: Boolean => + def readBoolean = prop { (b: Boolean) => val cfg = ConfigFactory.parseString(s"myValue = $b") booleanValueReader.read(cfg, "myValue") must beEqualTo(b) } - def readInt = prop { i: Int => + def readInt = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") intValueReader.read(cfg, "myValue") must beEqualTo(i) } - def readDoubleAsInt = prop { d: Double => + def readDoubleAsInt = prop { (d: Double) => (d >= Int.MinValue && d <= Int.MaxValue) ==> { val cfg = ConfigFactory.parseString(s"myValue = $d") intValueReader.read(cfg, "myValue") must beEqualTo(d.toInt) } } - def readLong = prop { l: Long => + def readLong = prop { (l: Long) => val cfg = ConfigFactory.parseString(s"myValue = $l") longValueReader.read(cfg, "myValue") must beEqualTo(l) } - def readIntAsLong = prop { i: Int => + def readIntAsLong = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") longValueReader.read(cfg, "myValue") must beEqualTo(i.toLong) } - def readDouble = prop { d: Double => + def readDouble = prop { (d: Double) => val cfg = ConfigFactory.parseString(s"myValue = $d") doubleValueReader.read(cfg, "myValue") must beEqualTo(d) } - def readIntAsDouble = prop { i: Int => + def readIntAsDouble = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") doubleValueReader.read(cfg, "myValue") must beEqualTo(i.toDouble) } diff --git a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala index 92b805b..2883b42 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala @@ -1,6 +1,7 @@ package net.ceedubs.ficus package readers +import scala.language.implicitConversions import com.typesafe.config.ConfigFactory import ConfigSerializerOps._ import shapeless.test.illTyped @@ -30,7 +31,7 @@ class ArbitraryTypeReaderSpec extends Spec { import ArbitraryTypeReaderSpec._ - def instantiateSingleParamApply = prop { foo2: String => + def instantiateSingleParamApply = prop { (foo2: String) => import Ficus.stringValueReader import ArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") @@ -39,7 +40,7 @@ class ArbitraryTypeReaderSpec extends Spec { instance.foo must_== foo2 } - def instantiateSingleParamApplyFromSelf = prop { foo2: String => + def instantiateSingleParamApplyFromSelf = prop { (foo2: String) => import Ficus.stringValueReader import ArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") @@ -48,7 +49,7 @@ class ArbitraryTypeReaderSpec extends Spec { instance.foo must_== foo2 } - def instantiateSingleParamConstructor = prop { foo: String => + def instantiateSingleParamConstructor = prop { (foo: String) => import Ficus.stringValueReader import ArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"singleParam { foo = ${foo.asConfigValue} }") @@ -80,7 +81,7 @@ class ArbitraryTypeReaderSpec extends Spec { (instance.foo must_== foo) and (instance.bar must_== bar) } - def multipleApply = prop { foo: String => + def multipleApply = prop { (foo: String) => import Ficus.stringValueReader import ArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"withMultipleApply { foo = ${foo.asConfigValue} }") @@ -89,7 +90,7 @@ class ArbitraryTypeReaderSpec extends Spec { instance.foo must_== foo } - def multipleConstructors = prop { foo: String => + def multipleConstructors = prop { (foo: String) => import Ficus.stringValueReader import ArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"withMultipleConstructors { foo = ${foo.asConfigValue} }") @@ -133,14 +134,14 @@ class ArbitraryTypeReaderSpec extends Spec { arbitraryTypeValueReader[WithOption].value.read(cfg, "withOption").option must_== Some("here") } - def ignoreApplyParamDefault = prop { foo: String => + def ignoreApplyParamDefault = prop { (foo: String) => import Ficus.{optionValueReader, stringValueReader} import ArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must_== foo } - def ignoreConstructorParamDefault = prop { foo: String => + def ignoreConstructorParamDefault = prop { (foo: String) => import Ficus.{optionValueReader, stringValueReader} import ArbitraryTypeReader._ val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") @@ -172,7 +173,7 @@ class ArbitraryTypeReaderSpec extends Spec { WithReaderInCompanion("from-companion") ==== cfg.as[WithReaderInCompanion]("withReaderInCompanion") } - def useNameMapper = prop { foo: String => + def useNameMapper = prop { (foo: String) => import Ficus.stringValueReader import ArbitraryTypeReader._ implicit val nameMapper: NameMapper = new NameMapper { diff --git a/src/test/scala/net/ceedubs/ficus/readers/BigNumberReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/BigNumberReadersSpec.scala index b71ee34..0927c20 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/BigNumberReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/BigNumberReadersSpec.scala @@ -23,17 +23,17 @@ class BigNumberReadersSpec extends Spec with BigNumberReaders { detect wrong type on malformed BigInt $readMalformedBigInt """ - def readDoubleAsBigDecimal = prop { d: Double => + def readDoubleAsBigDecimal = prop { (d: Double) => val cfg = ConfigFactory.parseString(s"myValue = $d") bigDecimalReader.read(cfg, "myValue") must beEqualTo(BigDecimal(d)) } - def readLongAsBigDecimal = prop { l: Long => + def readLongAsBigDecimal = prop { (l: Long) => val cfg = ConfigFactory.parseString(s"myValue = $l") bigDecimalReader.read(cfg, "myValue") must beEqualTo(BigDecimal(l)) } - def readIntAsBigDecimal = prop { i: Int => + def readIntAsBigDecimal = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") bigDecimalReader.read(cfg, "myValue") must beEqualTo(BigDecimal(i)) } @@ -54,14 +54,14 @@ class BigNumberReadersSpec extends Spec with BigNumberReaders { } */ - def readBigDecimalAsStringBigDecimal = prop { b: BigDecimal => + def readBigDecimalAsStringBigDecimal = prop { (b: BigDecimal) => scala.util.Try(BigDecimal(b.toString)).toOption.isDefined ==> { val cfg = ConfigFactory.parseString(s"myValue = ${b.toString}") bigDecimalReader.read(cfg, "myValue") must beEqualTo(BigDecimal(b.toString)) } } - def readBigIntAsStringBigDecimal = prop { b: BigInt => + def readBigIntAsStringBigDecimal = prop { (b: BigInt) => scala.util.Try(BigDecimal(b.toString)).toOption.isDefined ==> { val cfg = ConfigFactory.parseString(s"myValue = ${b.toString}") bigDecimalReader.read(cfg, "myValue") must beEqualTo(BigDecimal(b.toString)) @@ -74,31 +74,31 @@ class BigNumberReadersSpec extends Spec with BigNumberReaders { bigDecimalReader.read(cfg, "myValue") must throwA[WrongType] } - def readBigIntAsBigDecimal = prop { b: BigInt => + def readBigIntAsBigDecimal = prop { (b: BigInt) => scala.util.Try(BigDecimal(b)).toOption.isDefined ==> { val cfg = ConfigFactory.parseString(s"myValue = $b") bigDecimalReader.read(cfg, "myValue") must beEqualTo(BigDecimal(b)) } } - def readIntAsBigInt = prop { i: Int => + def readIntAsBigInt = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") bigIntReader.read(cfg, "myValue") must beEqualTo(BigInt(i)) } - def readLongAsBigInt = prop { l: Long => + def readLongAsBigInt = prop { (l: Long) => val cfg = ConfigFactory.parseString(s"myValue = $l") bigIntReader.read(cfg, "myValue") must beEqualTo(BigInt(l)) } - def readBigIntAsBigInt = prop { b: BigInt => + def readBigIntAsBigInt = prop { (b: BigInt) => scala.util.Try(BigInt(b.toString)).toOption.isDefined ==> { val cfg = ConfigFactory.parseString(s"myValue = $b") bigIntReader.read(cfg, "myValue") must beEqualTo(BigInt(b.toString)) } } - def readBigIntAsStringBigInt = prop { b: BigInt => + def readBigIntAsStringBigInt = prop { (b: BigInt) => scala.util.Try(BigInt(b.toString)).toOption.isDefined ==> { val cfg = ConfigFactory.parseString(s"myValue = ${b.toString}") bigIntReader.read(cfg, "myValue") must beEqualTo(BigInt(b.toString)) diff --git a/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala index 62215fa..855b786 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala @@ -1,6 +1,7 @@ package net.ceedubs.ficus package readers +import scala.language.implicitConversions import com.typesafe.config.ConfigFactory import com.typesafe.config.Config import net.ceedubs.ficus.Ficus._ @@ -54,12 +55,12 @@ class CaseClassReadersSpec extends Spec { cfg.as[SimpleCaseClass]("simple") must_== SimpleCaseClass(bool = false) } - def hydrateSimpleCaseClass = prop { bool: Boolean => + def hydrateSimpleCaseClass = prop { (bool: Boolean) => val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") cfg.as[SimpleCaseClass]("simple") must_== SimpleCaseClass(bool = bool) } - def hydrateSimpleCaseClassFromSelf = prop { bool: Boolean => + def hydrateSimpleCaseClassFromSelf = prop { (bool: Boolean) => val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") cfg.getConfig("simple").as[SimpleCaseClass] must_== SimpleCaseClass(bool = bool) } @@ -74,12 +75,12 @@ class CaseClassReadersSpec extends Spec { cfg.as[MultipleFields]("multipleFields") must_== MultipleFields(string = foo, long = long) } - def withOptionField = prop { s: String => + def withOptionField = prop { (s: String) => val cfg = ConfigFactory.parseString(s"""withOption { option = ${s.asConfigValue} }""") cfg.as[WithOption]("withOption") must_== WithOption(Some(s)) } - def withNestedCaseClass = prop { bool: Boolean => + def withNestedCaseClass = prop { (bool: Boolean) => val cfg = ConfigFactory.parseString(s""" |withNested { | simple { @@ -90,12 +91,12 @@ class CaseClassReadersSpec extends Spec { cfg.as[WithNestedCaseClass]("withNested") must_== WithNestedCaseClass(simple = SimpleCaseClass(bool = bool)) } - def topLevelValueClass = prop { int: Int => + def topLevelValueClass = prop { (int: Int) => val cfg = ConfigFactory.parseString(s"valueClass { int = $int }") cfg.as[ValueClass]("valueClass") must_== ValueClass(int) } - def nestedValueClass = prop { int: Int => + def nestedValueClass = prop { (int: Int) => val cfg = ConfigFactory.parseString(s""" |withNestedValueClass { | valueClass = { @@ -108,12 +109,12 @@ class CaseClassReadersSpec extends Spec { ) } - def companionImplicitTopLevel = prop { int: Int => + def companionImplicitTopLevel = prop { (int: Int) => val cfg = ConfigFactory.parseString(s"value = $int ") cfg.as[CompanionImplicit]("value") must_== CompanionImplicit(int) } - def nestedCompanionImplicit = prop { int: Int => + def nestedCompanionImplicit = prop { (int: Int) => val cfg = ConfigFactory.parseString(s""" |withNestedCompanionImplicit { | value = $int diff --git a/src/test/scala/net/ceedubs/ficus/readers/CollectionReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/CollectionReadersSpec.scala index 6c915e2..c40f595 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/CollectionReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/CollectionReadersSpec.scala @@ -31,7 +31,7 @@ class CollectionReadersSpec extends Spec with CollectionReaders { } def readStringMap = { - def reads[A: Arbitrary: ValueReader: ConfigSerializer] = prop { map: Map[String, A] => + def reads[A: Arbitrary: ValueReader: ConfigSerializer] = prop { (map: Map[String, A]) => val cfg = ConfigFactory.parseString(s"myValue = ${map.asConfigValue}") mapValueReader[A].read(cfg, "myValue") must beEqualTo(map) } @@ -70,7 +70,7 @@ class CollectionReadersSpec extends Spec with CollectionReaders { ) = { def reads[V](implicit arb: Arbitrary[C[V]], serializer: ConfigSerializer[C[V]], reader: ValueReader[C[V]]) = - prop { values: C[V] => + prop { (values: C[V]) => val cfg = ConfigFactory.parseString(s"myValue = ${values.asConfigValue}") reader.read(cfg, "myValue") must beEqualTo(values) } diff --git a/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala index a58aed5..4526acd 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala @@ -14,7 +14,7 @@ class ConfigReaderSpec extends Spec { implicitly read a ficus config itself $implicitlyReadFicusConfigFromSelf """ - def readConfig = prop { i: Int => + def readConfig = prop { (i: Int) => val cfg = ConfigFactory.parseString(s""" |myConfig { | myValue = $i @@ -23,7 +23,7 @@ class ConfigReaderSpec extends Spec { configValueReader.read(cfg, "myConfig").getInt("myValue") must beEqualTo(i) } - def implicitlyReadConfig = prop { i: Int => + def implicitlyReadConfig = prop { (i: Int) => val cfg = ConfigFactory.parseString(s""" |myConfig { | myValue = $i @@ -32,7 +32,7 @@ class ConfigReaderSpec extends Spec { cfg.as[Config]("myConfig").getInt("myValue") must beEqualTo(i) } - def readFicusConfig = prop { i: Int => + def readFicusConfig = prop { (i: Int) => val cfg = ConfigFactory.parseString(s""" |myConfig { | myValue = $i @@ -41,7 +41,7 @@ class ConfigReaderSpec extends Spec { ficusConfigValueReader.read(cfg, "myConfig").as[Int]("myValue") must beEqualTo(i) } - def implicitlyReadFicusConfig = prop { i: Int => + def implicitlyReadFicusConfig = prop { (i: Int) => val cfg = ConfigFactory.parseString(s""" |myConfig { | myValue = $i @@ -50,7 +50,7 @@ class ConfigReaderSpec extends Spec { cfg.as[FicusConfig]("myConfig").as[Int]("myValue") must beEqualTo(i) } - def implicitlyReadFicusConfigFromSelf = prop { i: Int => + def implicitlyReadFicusConfigFromSelf = prop { (i: Int) => val cfg = ConfigFactory.parseString(s""" |myConfig { | myValue = $i diff --git a/src/test/scala/net/ceedubs/ficus/readers/ConfigValueReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ConfigValueReaderSpec.scala index 3898982..7179fe7 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ConfigValueReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ConfigValueReaderSpec.scala @@ -15,42 +15,42 @@ class ConfigValueReaderSpec extends Spec with ConfigValueReader { read an object $readObject """ - def readBoolean = prop { b: Boolean => + def readBoolean = prop { (b: Boolean) => val cfg = ConfigFactory.parseString(s"myValue = $b") val read = configValueValueReader.read(cfg, "myValue") read.valueType must beEqualTo(ConfigValueType.BOOLEAN) read.unwrapped() must beEqualTo(b) } - def readInt = prop { i: Int => + def readInt = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") val read = configValueValueReader.read(cfg, "myValue") read.valueType must beEqualTo(ConfigValueType.NUMBER) read.unwrapped() must beEqualTo(int2Integer(i)) } - def readDouble = prop { d: Double => + def readDouble = prop { (d: Double) => val cfg = ConfigFactory.parseString(s"myValue = $d") val read = configValueValueReader.read(cfg, "myValue") read.valueType must beEqualTo(ConfigValueType.NUMBER) read.unwrapped() must beEqualTo(double2Double(d)) } - def readString = prop { s: String => + def readString = prop { (s: String) => val cfg = ConfigFactory.parseString(s"myValue = ${s.asConfigValue}") val read = configValueValueReader.read(cfg, "myValue") read.valueType must beEqualTo(ConfigValueType.STRING) read.unwrapped() must beEqualTo(s) } - def readObject = prop { i: Int => + def readObject = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = { i = $i }") val read = configValueValueReader.read(cfg, "myValue") read.valueType must beEqualTo(ConfigValueType.OBJECT) read.unwrapped() must beEqualTo(cfg.getValue("myValue").unwrapped()) } - def readList = prop { i: Int => + def readList = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = [ $i ]") val read = configValueValueReader.read(cfg, "myValue") read.valueType must beEqualTo(ConfigValueType.LIST) diff --git a/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala index 6df141a..c17508b 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala @@ -21,17 +21,17 @@ class DurationReadersSpec extends Spec with DurationReaders { read negative infinite values $readNegativeInf """ - def readMillis[T](reader: ValueReader[T]) = prop { i: Int => + def readMillis[T](reader: ValueReader[T]) = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") reader.read(cfg, "myValue") must beEqualTo(i millis) } - def readMinutes[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-1.5e8.toInt, 1.5e8.toInt)) { i: Int => + def readMinutes[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-1.5e8.toInt, 1.5e8.toInt)) { (i: Int) => val cfg = ConfigFactory.parseString("myValue = \"" + i + " minutes\"") reader.read(cfg, "myValue") must beEqualTo(i minutes) } - def readDaysUnit[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-106580, 106580)) { i: Int => + def readDaysUnit[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-106580, 106580)) { (i: Int) => val str = i.toString + " day" + (if (i == 1) "" else "s") val cfg = ConfigFactory.parseString(s"""myValue = "$str" """) reader.read(cfg, "myValue").toString must beEqualTo(str) @@ -39,7 +39,7 @@ class DurationReadersSpec extends Spec with DurationReaders { def readPositiveInf = { val positiveInf = List("Inf", "PlusInf", "\"+Inf\"") - positiveInf.forall { s: String => + positiveInf.forall { (s: String) => val cfg = ConfigFactory.parseString(s"myValue = $s") durationReader.read(cfg, "myValue") should be(Duration.Inf) } @@ -47,7 +47,7 @@ class DurationReadersSpec extends Spec with DurationReaders { def readNegativeInf = { val negativeInf = List("-Inf", "MinusInf") - negativeInf.forall { s: String => + negativeInf.forall { (s: String) => val cfg = ConfigFactory.parseString(s"myValue = $s") durationReader.read(cfg, "myValue") should be(Duration.MinusInf) } diff --git a/src/test/scala/net/ceedubs/ficus/readers/EitherReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/EitherReadersSpec.scala index 5729b2e..1dabee0 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/EitherReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/EitherReadersSpec.scala @@ -27,32 +27,32 @@ class EitherReadersSpec handle complex types $handleComplexTypes """ - def readRightSideString = prop { a: String => + def readRightSideString = prop { (a: String) => val cfg = a.toConfigValue.atKey("x") eitherReader[String, String].read(cfg, "x") must beEqualTo(Right(a)) } - def fallbackToLeftSideOnMissingKey = prop { a: String => + def fallbackToLeftSideOnMissingKey = prop { (a: String) => eitherReader[Option[String], String].read(ConfigFactory.empty(), "x") must beEqualTo(Left(None)) } - def fallbackToLeftSideOnBadRightValue = prop { a: Int => + def fallbackToLeftSideOnBadRightValue = prop { (a: Int) => val badVal = a.toString + "xx" eitherReader[String, Int].read(badVal.toConfigValue.atKey("x"), "x") must beEqualTo(Left(badVal)) } - def rightAndLeftFailure = prop { a: Int => + def rightAndLeftFailure = prop { (a: Int) => val badVal = a.toString + "xx" tryValueReader(eitherReader[Int, Int]).read(badVal.toConfigValue.atKey("x"), "x") must beAnInstanceOf[Failure[Int]] } - def rightSideTry = prop { a: Int => + def rightSideTry = prop { (a: Int) => val badVal = a.toString + "xx" eitherReader[Int, Try[Int]].read(a.toConfigValue.atKey("x"), "x") must beRight(a) eitherReader[Int, Try[Int]].read(badVal.toConfigValue.atKey("x"), "x") must beRight(beFailedTry[Int]) } - def leftSideTry = prop { a: Int => + def leftSideTry = prop { (a: Int) => val badVal = a.toString + "xx" eitherReader[Try[String], Int].read(badVal.toConfigValue.atKey("x"), "x") must beLeft( beSuccessfulTry[String](badVal) diff --git a/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala index 170eab5..1783340 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala @@ -15,9 +15,9 @@ class HyphenNameMapperSpec extends Spec with DataTables { def nonemptyStringListGen = nonEmptyListOf(alphaStr.suchThat(_.length > 1).map(_.toLowerCase)) - implicit def nonemptyStringList = Arbitrary(nonemptyStringListGen) + implicit def nonemptyStringList: Arbitrary[List[String]] = Arbitrary(nonemptyStringListGen) - def hyphenateCorrectly = prop { foos: List[String] => + def hyphenateCorrectly = prop { (foos: List[String]) => val camelCased = (foos.head +: foos.tail.map(_.capitalize)).mkString val hyphenated = foos.mkString("-").toLowerCase diff --git a/src/test/scala/net/ceedubs/ficus/readers/OptionReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/OptionReadersSpec.scala index 7cc8061..7fc552c 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/OptionReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/OptionReadersSpec.scala @@ -10,7 +10,7 @@ class OptionReadersSpec extends Spec with OptionReader with AnyValReaders { return a None for a non-existing value $optionNone """ - def optionSome = prop { i: Int => + def optionSome = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") optionValueReader[Int].read(cfg, "myValue") must beSome(i) } diff --git a/src/test/scala/net/ceedubs/ficus/readers/StringReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/StringReaderSpec.scala index e8c8404..7c670dc 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/StringReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/StringReaderSpec.scala @@ -11,7 +11,7 @@ class StringReaderSpec extends Spec with StringReader { read a String $readString """ - def readString = prop { string: String => + def readString = prop { (string: String) => val cfg = ConfigFactory.parseString(s"myValue = ${string.asConfigValue}") stringValueReader.read(cfg, "myValue") must beEqualTo(string) } diff --git a/src/test/scala/net/ceedubs/ficus/readers/SymbolReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/SymbolReaderSpec.scala index 2e5ffb5..9a7aa05 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/SymbolReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/SymbolReaderSpec.scala @@ -10,7 +10,7 @@ class SymbolReaderSpec extends Spec with SymbolReader { read a Symbol $readSymbol """ - def readSymbol = prop { string: String => + def readSymbol = prop { (string: String) => val cfg = ConfigFactory.parseString(s"myValue = ${string.asConfigValue}") symbolValueReader.read(cfg, "myValue") must beEqualTo(Symbol(string)) } diff --git a/src/test/scala/net/ceedubs/ficus/readers/TryReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/TryReaderSpec.scala index f787a77..4c0ea6d 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/TryReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/TryReaderSpec.scala @@ -14,7 +14,7 @@ class TryReaderSpec extends Spec with TryReader with AnyValReaders { handle an unexpected exception $unexpectedException """ - def successWhenPresent = prop { i: Int => + def successWhenPresent = prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") tryValueReader[Int].read(cfg, "myValue") must beSuccessfulTry[Int].withValue(i) } @@ -32,7 +32,7 @@ class TryReaderSpec extends Spec with TryReader with AnyValReaders { tryValueReader[String].read(cfg, "myValue") must beFailedTry[String].withThrowable[NullPointerException]("oops") } - def unexpectedException = prop { up: Throwable => + def unexpectedException = prop { (up: Throwable) => val cfg = ConfigFactory.parseString("myValue = true") implicit val stringValueReader: ValueReader[String] = new ValueReader[String] { def read(config: Config, path: String): String = throw up diff --git a/src/test/scala/net/ceedubs/ficus/readers/ValueReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ValueReaderSpec.scala index 6ddad0e..221050d 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ValueReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ValueReaderSpec.scala @@ -16,7 +16,7 @@ class ValueReaderSpec extends Spec { def transformAsFunctor = { val plusOneReader = ValueReader[Int].map(_ + 1) - prop { i: Int => + prop { (i: Int) => val cfg = ConfigFactory.parseString(s"myValue = $i") plusOneReader.read(cfg, "myValue") must beEqualTo(i + 1) } From c175a06f23ba28d6fb1eadc8a47bca21641434af Mon Sep 17 00:00:00 2001 From: Hugh Simpson Date: Tue, 27 Jul 2021 00:09:27 +0100 Subject: [PATCH 2/3] wippery --- .../net/ceedubs/ficus/Ficus.scala | 1 - .../net/ceedubs/ficus/FicusConfig.scala | 1 - .../scala-3/net/ceedubs/ficus/Ficus.scala | 37 ++ .../net/ceedubs/ficus/FicusConfig.scala | 22 + .../ficus/readers/CollectionReaders.scala | 36 +- .../readers/EnumerationReadersSpec.scala | 0 .../readers/EnumerationReadersSpec.scala | 65 ++ .../net/ceedubs/ficus/ConfigSerializer.scala | 2 - .../scala/net/ceedubs/ficus/ExampleSpec.scala | 172 +++--- .../scala/net/ceedubs/ficus/Examples.scala | 16 +- .../net/ceedubs/ficus/FicusConfigSpec.scala | 136 ++--- .../readers/ArbitraryTypeReaderSpec.scala | 559 +++++++++--------- .../ficus/readers/CaseClassReadersSpec.scala | 313 +++++----- .../ficus/readers/ConfigReaderSpec.scala | 122 ++-- .../ficus/readers/DurationReadersSpec.scala | 110 ++-- .../ficus/readers/HyphenNameMapperSpec.scala | 4 +- .../readers/ISOZonedDateTimeReaderSpec.scala | 6 +- .../ficus/readers/LocalDateReaderSpec.scala | 8 +- .../ficus/readers/PeriodReaderSpec.scala | 15 +- .../ceedubs/ficus/readers/URLReaderSpec.scala | 56 +- 20 files changed, 917 insertions(+), 764 deletions(-) rename src/main/{scala => scala-2}/net/ceedubs/ficus/Ficus.scala (93%) rename src/main/{scala => scala-2}/net/ceedubs/ficus/FicusConfig.scala (96%) create mode 100644 src/main/scala-3/net/ceedubs/ficus/Ficus.scala create mode 100644 src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala rename src/test/{scala => scala-2}/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala (100%) create mode 100644 src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala diff --git a/src/main/scala/net/ceedubs/ficus/Ficus.scala b/src/main/scala-2/net/ceedubs/ficus/Ficus.scala similarity index 93% rename from src/main/scala/net/ceedubs/ficus/Ficus.scala rename to src/main/scala-2/net/ceedubs/ficus/Ficus.scala index 98fb8be..9e36538 100644 --- a/src/main/scala/net/ceedubs/ficus/Ficus.scala +++ b/src/main/scala-2/net/ceedubs/ficus/Ficus.scala @@ -2,7 +2,6 @@ package net.ceedubs.ficus import com.typesafe.config.Config import net.ceedubs.ficus.readers._ -import scala.language.implicitConversions trait FicusInstances extends AnyValReaders diff --git a/src/main/scala/net/ceedubs/ficus/FicusConfig.scala b/src/main/scala-2/net/ceedubs/ficus/FicusConfig.scala similarity index 96% rename from src/main/scala/net/ceedubs/ficus/FicusConfig.scala rename to src/main/scala-2/net/ceedubs/ficus/FicusConfig.scala index c18193e..7d8ad12 100644 --- a/src/main/scala/net/ceedubs/ficus/FicusConfig.scala +++ b/src/main/scala-2/net/ceedubs/ficus/FicusConfig.scala @@ -2,7 +2,6 @@ package net.ceedubs.ficus import com.typesafe.config.Config import net.ceedubs.ficus.readers.{AllValueReaderInstances, ValueReader} -import scala.language.implicitConversions trait FicusConfig { def config: Config diff --git a/src/main/scala-3/net/ceedubs/ficus/Ficus.scala b/src/main/scala-3/net/ceedubs/ficus/Ficus.scala new file mode 100644 index 0000000..1ca9cba --- /dev/null +++ b/src/main/scala-3/net/ceedubs/ficus/Ficus.scala @@ -0,0 +1,37 @@ +package net.ceedubs.ficus + +import com.typesafe.config.Config +import net.ceedubs.ficus.readers._ + +trait FicusInstances + extends AnyValReaders + with StringReader + with SymbolReader + with OptionReader + with CollectionReaders + with ConfigReader + with DurationReaders + with TryReader + with ConfigValueReader + with BigNumberReaders + with ISOZonedDateTimeReader + with PeriodReader + with LocalDateReader + with URIReaders + with URLReader + with InetSocketAddressReaders + +object Ficus extends FicusInstances { + extension (config: Config) { + def to[A](path: String)(implicit reader: ValueReader[A]): A = reader.read(config, path) + + def as[A](implicit reader: ValueReader[A]): A = to(".") + + def getAs[A](path: String)(implicit reader: ValueReader[Option[A]]): Option[A] = reader.read(config, path) + + def getOrElse[A](path: String, default: => A)(implicit reader: ValueReader[Option[A]]): A = + getAs[A](path).getOrElse(default) + + def apply[A](key: ConfigKey[A])(implicit reader: ValueReader[A]): A = to[A](key.path) + } +} diff --git a/src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala b/src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala new file mode 100644 index 0000000..4e72dde --- /dev/null +++ b/src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala @@ -0,0 +1,22 @@ +package net.ceedubs.ficus + +import com.typesafe.config.Config +import net.ceedubs.ficus.readers.{AllValueReaderInstances, ValueReader} + +trait FicusConfig { + def config: Config + + def as[A](path: String)(implicit reader: ValueReader[A]): A = reader.read(config, path) + + def as[A](implicit reader: ValueReader[A]): A = as(".") + + def getAs[A](path: String)(implicit reader: ValueReader[Option[A]]): Option[A] = reader.read(config, path) + + def getOrElse[A](path: String, default: => A)(implicit reader: ValueReader[Option[A]]): A = + getAs[A](path).getOrElse(default) + + def apply[A](key: ConfigKey[A])(implicit reader: ValueReader[A]): A = as[A](key.path) +} + +final case class SimpleFicusConfig(config: Config) extends FicusConfig + diff --git a/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala b/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala index 96ccee2..464c41d 100644 --- a/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala +++ b/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala @@ -1,7 +1,41 @@ package net.ceedubs.ficus.readers +import com.typesafe.config.{Config, ConfigUtil} +import scala.collection.{Factory, IterableFactory} +import scala.collection.generic.CanBuildFrom +import scala.collection.mutable.Builder +import scala.jdk.CollectionConverters._ +import scala.language.postfixOps +import scala.language.higherKinds + trait CollectionReaders { + private[this] val DummyPathValue: String = "collection-entry-path" + implicit def traversableReader[C[_], A](implicit + entryReader: ValueReader[A], + factory: Factory[A, C[A]] + ): ValueReader[C[A]] = new ValueReader[C[A]] { + def read(config: Config, path: String): C[A] = { + val list = config.getList(path).asScala + val builder: Builder[A, C[A]] = factory.newBuilder + builder.sizeHint(list.size) + list foreach { entry => + val entryConfig = entry.atPath(DummyPathValue) + builder += entryReader.read(entryConfig, DummyPathValue) + } + builder.result() + } + } + implicit def mapValueReader[A](implicit entryReader: ValueReader[A]): ValueReader[Map[String, A]] = + new ValueReader[Map[String, A]] { + def read(config: Config, path: String): Map[String, A] = { + val relativeConfig = config.getConfig(path) + relativeConfig.root().entrySet().asScala map { entry => + val key = entry.getKey + key -> entryReader.read(relativeConfig, ConfigUtil.quoteString(key)) + } toMap + } + } } -object CollectionReaders extends CollectionReaders \ No newline at end of file +object CollectionReaders extends CollectionReaders diff --git a/src/test/scala/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala b/src/test/scala-2/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala similarity index 100% rename from src/test/scala/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala rename to src/test/scala-2/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala diff --git a/src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala b/src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala new file mode 100644 index 0000000..e9c83a2 --- /dev/null +++ b/src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala @@ -0,0 +1,65 @@ +//package net.ceedubs.ficus.readers +// +//import com.typesafe.config.{ConfigException, ConfigFactory} +//import net.ceedubs.ficus.Spec +//import EnumerationReadersSpec._ +// +//import scala.reflect.ClassTag +// +//class EnumerationReadersSpec extends Spec with EnumerationReader { +// def is = s2""" +// An enumeration value reader should +// map a string value to its enumeration counterpart $successStringMapping +// map a int value to its enumeration counterpart $successIntMapping +// throw exception if value couldn't be converted to enum value $invalidMapping +// throw exception if enumeration is contained in a class or trait $notInstantiable +// throw exception if enumeration is not an object $notObject +// """ +// +//// def successStringMapping = { +//// val cfg = ConfigFactory.parseString("myValue = SECOND") +//// implicit val classTag = ClassTag[StringValueEnum.type](StringValueEnum.getClass) +//// enumerationValueReader[StringValueEnum.type].read(cfg, "myValue") must be equalTo StringValueEnum.second +//// } +// +// def successIntMapping = { +// val cfg = ConfigFactory.parseString("myValue = second") +// implicit val classTag = ClassTag[IntValueEnum.type](IntValueEnum.getClass) +// enumerationValueReader[IntValueEnum.type].read(cfg, "myValue") must be equalTo IntValueEnum.second +// } +// +// def invalidMapping = { +// val cfg = ConfigFactory.parseString("myValue = fourth") +// implicit val classTag = ClassTag[StringValueEnum.type](StringValueEnum.getClass) +// enumerationValueReader[StringValueEnum.type].read(cfg, "myValue") must throwA[ConfigException.BadValue] +// } +// +// def notInstantiable = { +// val cfg = ConfigFactory.parseString("myValue = fourth") +// implicit val classTag = ClassTag[InnerEnum.type](InnerEnum.getClass) +// enumerationValueReader[InnerEnum.type].read(cfg, "myValue") must throwA[ConfigException.Generic] +// } +// +// def notObject = { +// val cfg = ConfigFactory.parseString("myValue = fourth") +// implicit val classTag = ClassTag[NotObject](classOf[NotObject]) +// enumerationValueReader[NotObject].read(cfg, "myValue") must throwA[ConfigException.Generic] +// } +// +// enum InnerEnum +//} +// +//object EnumerationReadersSpec { +// +//// enum StringValueEnum(val name: String) { +//// case first extends StringValueEnum("FIRST") +//// case second extends StringValueEnum("SECOND") +//// case third extends StringValueEnum("THIRD") +//// } +// +// enum IntValueEnum { +// case first, second, third +// } +// +// enum NotObject +//} \ No newline at end of file diff --git a/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala b/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala index 7e36580..3bbf541 100644 --- a/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala +++ b/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala @@ -2,8 +2,6 @@ package net.ceedubs.ficus import com.typesafe.config.{ConfigFactory, ConfigUtil, ConfigValue} -import scala.language.implicitConversions - trait ConfigSerializer[A] { def serialize(a: A): String } diff --git a/src/test/scala/net/ceedubs/ficus/ExampleSpec.scala b/src/test/scala/net/ceedubs/ficus/ExampleSpec.scala index fdf1bfd..992f627 100644 --- a/src/test/scala/net/ceedubs/ficus/ExampleSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/ExampleSpec.scala @@ -1,86 +1,86 @@ -package net.ceedubs.ficus - -import org.specs2.mutable.Specification -import com.typesafe.config.{Config, ConfigFactory} -import Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ -import net.ceedubs.ficus.readers.EnumerationReader._ -import net.ceedubs.ficus.readers.ValueReader - -case class ServiceConfig(urls: Set[String], maxConnections: Int, httpsRequired: Boolean = false) - -object Country extends Enumeration { - val DE = Value("DE") - val IT = Value("IT") - val NL = Value("NL") - val US = Value("US") - val GB = Value("GB") -} - -class ExampleSpec extends Specification { - - // an example config snippet for us to work with - val config = ConfigFactory.parseString(""" - |services { - | users { - | urls = ["localhost:8001"] - | maxConnections = 100 - | httpsRequired = true - | } - | analytics { - | urls = ["localhost:8002", "localhost:8003"] - | maxConnections = 25 - | } - |} - |countries = [DE, US, GB] - """.stripMargin) - - "Ficus config" should { - "make Typesafe config more Scala-friendly" in { - val userServiceConfig = config.as[Config]("services.users") - userServiceConfig.as[Set[String]]("urls") must beEqualTo(Set("localhost:8001")) - userServiceConfig.as[Int]("maxConnections") must beEqualTo(100) - userServiceConfig.as[Option[Boolean]]("httpsRequired") must beSome(true) - - val analyticsServiceConfig = config.as[Config]("services.analytics") - analyticsServiceConfig.as[List[String]]("urls") must beEqualTo(List("localhost:8002", "localhost:8003")) - val analyticsServiceRequiresHttps = analyticsServiceConfig.as[Option[Boolean]]("httpsRequired") getOrElse false - analyticsServiceRequiresHttps must beFalse - - config.as[Seq[Country.Value]]("countries") must be equalTo Seq(Country.DE, Country.US, Country.GB) - } - - "Automagically be able to hydrate arbitrary types from config" in { - // Tkere are a few restrictions on types that can be read. See README file in root of project - val analyticsConfig = config.as[ServiceConfig]("services.analytics") - analyticsConfig.maxConnections must beEqualTo(25) - // since this value isn't in the config, it will fall back to the default for the case class - analyticsConfig.httpsRequired must beFalse - } - - "Be easily extensible" in { - // If we want a value reader that defaults httpsRequired to true instead of false (the default on the case - // class) we can define a custom value reader for ServiceConfig - implicit val serviceConfigReader: ValueReader[ServiceConfig] = ValueReader.relative { serviceConfig => - ServiceConfig( - urls = serviceConfig.as[Set[String]]("urls"), - maxConnections = serviceConfig.getInt("maxConnections"), // the old-fashioned way is fine too! - httpsRequired = serviceConfig.as[Option[Boolean]]("httpsRequired") getOrElse true - ) - } - - // so we don't have to add a "services." prefix for each service - val servicesConfig = config.as[Config]("services") - - val analyticsServiceConfig: ServiceConfig = servicesConfig.as[ServiceConfig]("analytics") - // the analytics service config doesn't define an "httpsRequired" value, but the serviceConfigReader defaults - // to true if it is empty with its 'getOrElse true' on the extracted Option - analyticsServiceConfig.httpsRequired must beTrue - - val userServiceConfig: ServiceConfig = servicesConfig.as[ServiceConfig]("users") - - val servicesMap = config.as[Map[String, ServiceConfig]]("services") - servicesMap must beEqualTo(Map("users" -> userServiceConfig, "analytics" -> analyticsServiceConfig)) - } - } -} +//package net.ceedubs.ficus +// +//import org.specs2.mutable.Specification +//import com.typesafe.config.{Config, ConfigFactory} +//import Ficus._ +//import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +//import net.ceedubs.ficus.readers.EnumerationReader._ +//import net.ceedubs.ficus.readers.ValueReader +// +//case class ServiceConfig(urls: Set[String], maxConnections: Int, httpsRequired: Boolean = false) +// +//object Country extends Enumeration { +// val DE = Value("DE") +// val IT = Value("IT") +// val NL = Value("NL") +// val US = Value("US") +// val GB = Value("GB") +//} +// +//class ExampleSpec extends Specification { +// +// // an example config snippet for us to work with +// val config = ConfigFactory.parseString(""" +// |services { +// | users { +// | urls = ["localhost:8001"] +// | maxConnections = 100 +// | httpsRequired = true +// | } +// | analytics { +// | urls = ["localhost:8002", "localhost:8003"] +// | maxConnections = 25 +// | } +// |} +// |countries = [DE, US, GB] +// """.stripMargin) +// +// "Ficus config" should { +// "make Typesafe config more Scala-friendly" in { +// val userServiceConfig = config.as[Config]("services.users") +// userServiceConfig.as[Set[String]]("urls") must beEqualTo(Set("localhost:8001")) +// userServiceConfig.as[Int]("maxConnections") must beEqualTo(100) +// userServiceConfig.as[Option[Boolean]]("httpsRequired") must beSome(true) +// +// val analyticsServiceConfig = config.as[Config]("services.analytics") +// analyticsServiceConfig.as[List[String]]("urls") must beEqualTo(List("localhost:8002", "localhost:8003")) +// val analyticsServiceRequiresHttps = analyticsServiceConfig.as[Option[Boolean]]("httpsRequired") getOrElse false +// analyticsServiceRequiresHttps must beFalse +// +// config.as[Seq[Country.Value]]("countries") must be equalTo Seq(Country.DE, Country.US, Country.GB) +// } +// +// "Automagically be able to hydrate arbitrary types from config" in { +// // Tkere are a few restrictions on types that can be read. See README file in root of project +// val analyticsConfig = config.as[ServiceConfig]("services.analytics") +// analyticsConfig.maxConnections must beEqualTo(25) +// // since this value isn't in the config, it will fall back to the default for the case class +// analyticsConfig.httpsRequired must beFalse +// } +// +// "Be easily extensible" in { +// // If we want a value reader that defaults httpsRequired to true instead of false (the default on the case +// // class) we can define a custom value reader for ServiceConfig +// implicit val serviceConfigReader: ValueReader[ServiceConfig] = ValueReader.relative { serviceConfig => +// ServiceConfig( +// urls = serviceConfig.as[Set[String]]("urls"), +// maxConnections = serviceConfig.getInt("maxConnections"), // the old-fashioned way is fine too! +// httpsRequired = serviceConfig.as[Option[Boolean]]("httpsRequired") getOrElse true +// ) +// } +// +// // so we don't have to add a "services." prefix for each service +// val servicesConfig = config.as[Config]("services") +// +// val analyticsServiceConfig: ServiceConfig = servicesConfig.as[ServiceConfig]("analytics") +// // the analytics service config doesn't define an "httpsRequired" value, but the serviceConfigReader defaults +// // to true if it is empty with its 'getOrElse true' on the extracted Option +// analyticsServiceConfig.httpsRequired must beTrue +// +// val userServiceConfig: ServiceConfig = servicesConfig.as[ServiceConfig]("users") +// +// val servicesMap = config.as[Map[String, ServiceConfig]]("services") +// servicesMap must beEqualTo(Map("users" -> userServiceConfig, "analytics" -> analyticsServiceConfig)) +// } +// } +//} diff --git a/src/test/scala/net/ceedubs/ficus/Examples.scala b/src/test/scala/net/ceedubs/ficus/Examples.scala index 1e855f3..565a756 100644 --- a/src/test/scala/net/ceedubs/ficus/Examples.scala +++ b/src/test/scala/net/ceedubs/ficus/Examples.scala @@ -11,19 +11,19 @@ class Examples { val config: Config = ConfigFactory.load() // standard Typesafe Config // Note: explicit typing isn't necessary. It's just in these examples to make it clear what the return types are. - // This line could instead be: val appName = config.as[String]("app.name") - val appName: String = config.as[String]("app.name") // equivalent to config.getString("app.name") + // This line could instead be: val appName = config.to[String]("app.name") + val appName: String = config.to[String]("app.name") // equivalent to config.getString("app.name") - // config.as[Option[Boolean]]("preloadCache") will return None if preloadCache isn't defined in the config - val preloadCache: Boolean = config.as[Option[Boolean]]("preloadCache").getOrElse(false) + // config.to[Option[Boolean]]("preloadCache") will return None if preloadCache isn't defined in the config + val preloadCache: Boolean = config.to[Option[Boolean]]("preloadCache").getOrElse(false) - val adminUserIds: Set[Long] = config.as[Set[Long]]("adminIds") + val adminUserIds: Set[Long] = config.to[Set[Long]]("adminIds") - // something such as "15 minutes" can be converted to a FiniteDuration - val retryInterval: FiniteDuration = config.as[FiniteDuration]("retryInterval") + // something such to "15 minutes" can be converted to a FiniteDuration + val retryInterval: FiniteDuration = config.to[FiniteDuration]("retryInterval") // can hydrate most arbitrary types // it first tries to use an apply method on the companion object and falls back to the primary constructor // if values are not in the config, they will fall back to the default value on the class/apply method - val someCaseClass: SomeCaseClass = config.as[SomeCaseClass]("someCaseClass") + val someCaseClass: SomeCaseClass = config.to[SomeCaseClass]("someCaseClass") } diff --git a/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala b/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala index 8d37706..6ce9650 100644 --- a/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/FicusConfigSpec.scala @@ -1,68 +1,68 @@ -package net.ceedubs.ficus - -import com.typesafe.config.{Config, ConfigFactory} -import Ficus.{booleanValueReader, optionValueReader, stringValueReader, toFicusConfig} -import net.ceedubs.ficus.readers.ValueReader - -class FicusConfigSpec extends Spec { - def is = s2""" - A Ficus config should - be implicitly converted from a Typesafe config $implicitlyConverted - read a value with a value reader $readAValue - get an existing value as a Some $getAsSome - get a missing value as a None $getAsNone - getOrElse an existing value as asked type $getOrElseFromConfig - getOrElse a missing value with default value $getOrElseFromDefault - getOrElse an existing value as asked type with customer reader $getOrElseFromConfigWithCustomValueReader - accept a CongigKey and return the appropriate type $acceptAConfigKey - """ - - def implicitlyConverted = { - val cfg = ConfigFactory.parseString("myValue = true") - cfg.as[Boolean]("myValue") must beTrue - } - - def readAValue = prop { (b: Boolean) => - val cfg = ConfigFactory.parseString(s"myValue = $b") - cfg.as[Boolean]("myValue") must beEqualTo(b) - } - - def getAsSome = prop { (b: Boolean) => - val cfg = ConfigFactory.parseString(s"myValue = $b") - cfg.getAs[Boolean]("myValue") must beSome(b) - } - - def getAsNone = { - val cfg = ConfigFactory.parseString("myValue = true") - cfg.getAs[Boolean]("nonValue") must beNone - } - - def getOrElseFromConfig = { - val configString = "arealstring" - val cfg = ConfigFactory.parseString(s"myValue = $configString") - cfg.getOrElse("myValue", "notarealstring") must beEqualTo(configString) - } - - def getOrElseFromDefault = { - val cfg = ConfigFactory.parseString("myValue = arealstring") - val default = "adefaultstring" - cfg.getOrElse("nonValue", default) must beEqualTo(default) - } - - def getOrElseFromConfigWithCustomValueReader = { - val cfg = ConfigFactory.parseString("myValue = 124") - val default = 23.toByte - - implicit val byteReader = new ValueReader[Byte] { - def read(config: Config, path: String): Byte = config.getInt(path).toByte - } - - cfg.getOrElse("myValue", default) must beEqualTo(124.toByte) - } - - def acceptAConfigKey = prop { (b: Boolean) => - val cfg = ConfigFactory.parseString(s"myValue = $b") - val key: ConfigKey[Boolean] = SimpleConfigKey("myValue") - cfg(key) must beEqualTo(b) - } -} +//package net.ceedubs.ficus +// +//import com.typesafe.config.{Config, ConfigFactory} +//import Ficus.{booleanValueReader, optionValueReader, stringValueReader, toFicusConfig} +//import net.ceedubs.ficus.readers.ValueReader +// +//class FicusConfigSpec extends Spec { +// def is = s2""" +// A Ficus config should +// be implicitly converted from a Typesafe config $implicitlyConverted +// read a value with a value reader $readAValue +// get an existing value as a Some $getAsSome +// get a missing value as a None $getAsNone +// getOrElse an existing value as asked type $getOrElseFromConfig +// getOrElse a missing value with default value $getOrElseFromDefault +// getOrElse an existing value as asked type with customer reader $getOrElseFromConfigWithCustomValueReader +// accept a CongigKey and return the appropriate type $acceptAConfigKey +// """ +// +// def implicitlyConverted = { +// val cfg = ConfigFactory.parseString("myValue = true") +// cfg.as[Boolean]("myValue") must beTrue +// } +// +// def readAValue = prop { (b: Boolean) => +// val cfg = ConfigFactory.parseString(s"myValue = $b") +// cfg.as[Boolean]("myValue") must beEqualTo(b) +// } +// +// def getAsSome = prop { (b: Boolean) => +// val cfg = ConfigFactory.parseString(s"myValue = $b") +// cfg.getAs[Boolean]("myValue") must beSome(b) +// } +// +// def getAsNone = { +// val cfg = ConfigFactory.parseString("myValue = true") +// cfg.getAs[Boolean]("nonValue") must beNone +// } +// +// def getOrElseFromConfig = { +// val configString = "arealstring" +// val cfg = ConfigFactory.parseString(s"myValue = $configString") +// cfg.getOrElse("myValue", "notarealstring") must beEqualTo(configString) +// } +// +// def getOrElseFromDefault = { +// val cfg = ConfigFactory.parseString("myValue = arealstring") +// val default = "adefaultstring" +// cfg.getOrElse("nonValue", default) must beEqualTo(default) +// } +// +// def getOrElseFromConfigWithCustomValueReader = { +// val cfg = ConfigFactory.parseString("myValue = 124") +// val default = 23.toByte +// +// implicit val byteReader = new ValueReader[Byte] { +// def read(config: Config, path: String): Byte = config.getInt(path).toByte +// } +// +// cfg.getOrElse("myValue", default) must beEqualTo(124.toByte) +// } +// +// def acceptAConfigKey = prop { (b: Boolean) => +// val cfg = ConfigFactory.parseString(s"myValue = $b") +// val key: ConfigKey[Boolean] = SimpleConfigKey("myValue") +// cfg(key) must beEqualTo(b) +// } +//} diff --git a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala index 2883b42..d412be3 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala @@ -1,280 +1,279 @@ -package net.ceedubs.ficus -package readers - -import scala.language.implicitConversions -import com.typesafe.config.ConfigFactory -import ConfigSerializerOps._ -import shapeless.test.illTyped - -class ArbitraryTypeReaderSpec extends Spec { - def is = s2""" - An arbitrary type reader should - instantiate with a single-param apply method $instantiateSingleParamApply - instantiate with a single-param apply method from config itself $instantiateSingleParamApplyFromSelf - instantiate with no apply method but a single constructor with a single param $instantiateSingleParamConstructor - instantiate with a multi-param apply method $instantiateMultiParamApply - instantiate with no apply method but a single constructor with multiple params $instantiateMultiParamConstructor - instantiate with multiple apply methods if only one returns the correct type $multipleApply - instantiate with primary constructor when no apply methods and multiple constructors $multipleConstructors - use another implicit value reader for a field $withOptionField - fall back to a default value on an apply method $fallBackToApplyMethodDefaultValue - fall back to default values on an apply method if base key isn't in config $fallBackToApplyMethodDefaultValueNoKey - fall back to a default value on a constructor arg $fallBackToConstructorDefaultValue - fall back to a default values on a constructor if base key isn't in config $fallBackToConstructorDefaultValueNoKey - ignore a default value on an apply method if a value is in config $ignoreApplyParamDefault - ignore a default value in a constructor if a value is in config $ignoreConstructorParamDefault - allow overriding of option reader for default values $overrideOptionReaderForDefault - not choose between multiple Java constructors $notChooseBetweenJavaConstructors - not be prioritized over a Reader defined in a type's companion object (when Ficus._ is imported) $notTrumpCompanionReader - use name mapper $useNameMapper - """ - - import ArbitraryTypeReaderSpec._ - - def instantiateSingleParamApply = prop { (foo2: String) => - import Ficus.stringValueReader - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") - val instance: WithSimpleCompanionApply = - arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg, "simple") - instance.foo must_== foo2 - } - - def instantiateSingleParamApplyFromSelf = prop { (foo2: String) => - import Ficus.stringValueReader - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") - val instance: WithSimpleCompanionApply = - arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg.getConfig("simple"), ".") - instance.foo must_== foo2 - } - - def instantiateSingleParamConstructor = prop { (foo: String) => - import Ficus.stringValueReader - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"singleParam { foo = ${foo.asConfigValue} }") - val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") - instance.getFoo must_== foo - } - - def instantiateMultiParamApply = prop { (foo: String, bar: Int) => - import Ficus.{intValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s""" - |multi { - | foo = ${foo.asConfigValue} - | bar = $bar - |}""".stripMargin) - val instance: WithMultiCompanionApply = arbitraryTypeValueReader[WithMultiCompanionApply].value.read(cfg, "multi") - (instance.foo must_== foo) and (instance.bar must_== bar) - } - - def instantiateMultiParamConstructor = prop { (foo: String, bar: Int) => - import Ficus.{intValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s""" - |multi { - | foo = ${foo.asConfigValue} - | bar = $bar - |}""".stripMargin) - val instance: ClassWithMultipleParams = arbitraryTypeValueReader[ClassWithMultipleParams].value.read(cfg, "multi") - (instance.foo must_== foo) and (instance.bar must_== bar) - } - - def multipleApply = prop { (foo: String) => - import Ficus.stringValueReader - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"withMultipleApply { foo = ${foo.asConfigValue} }") - val instance: WithMultipleApplyMethods = - arbitraryTypeValueReader[WithMultipleApplyMethods].value.read(cfg, "withMultipleApply") - instance.foo must_== foo - } - - def multipleConstructors = prop { (foo: String) => - import Ficus.stringValueReader - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"withMultipleConstructors { foo = ${foo.asConfigValue} }") - val instance: ClassWithMultipleConstructors = - arbitraryTypeValueReader[ClassWithMultipleConstructors].value.read(cfg, "withMultipleConstructors") - instance.foo must_== foo - } - - def fallBackToApplyMethodDefaultValue = { - import Ficus.{optionValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString("withDefault { }") - arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must_== "defaultFoo" - } - - def fallBackToApplyMethodDefaultValueNoKey = { - import Ficus.{optionValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString("") - arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must_== "defaultFoo" - } - - def fallBackToConstructorDefaultValue = { - import Ficus.{optionValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString("withDefault { }") - arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must_== "defaultFoo" - } - - def fallBackToConstructorDefaultValueNoKey = { - import Ficus.{optionValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString("") - arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must_== "defaultFoo" - } - - def withOptionField = { - import Ficus.{optionValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString("""withOption { option = "here" }""") - arbitraryTypeValueReader[WithOption].value.read(cfg, "withOption").option must_== Some("here") - } - - def ignoreApplyParamDefault = prop { (foo: String) => - import Ficus.{optionValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") - arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must_== foo - } - - def ignoreConstructorParamDefault = prop { (foo: String) => - import Ficus.{optionValueReader, stringValueReader} - import ArbitraryTypeReader._ - val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") - arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must_== foo - } - - def overrideOptionReaderForDefault = { - import ArbitraryTypeReader._ - implicit val stringOptionReader: ValueReader[Option[String]] = Ficus.stringValueReader map { s => - if (s.isEmpty) None else Some(s) - } - val cfg = ConfigFactory.parseString("""withDefault { foo = "" }""") - arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") - } - - def notChooseBetweenJavaConstructors = { - illTyped("implicitly[ValueReader[String]]") - illTyped("implicitly[ValueReader[Long]]") - illTyped("implicitly[ValueReader[Int]]") - illTyped("implicitly[ValueReader[Float]]") - illTyped("implicitly[ValueReader[Double]]") - illTyped("implicitly[ValueReader[Char]]") - success // failure would result in compile error - } - - def notTrumpCompanionReader = { - import Ficus._ - val cfg = ConfigFactory.parseString("""withReaderInCompanion { foo = "bar" }""") - WithReaderInCompanion("from-companion") ==== cfg.as[WithReaderInCompanion]("withReaderInCompanion") - } - - def useNameMapper = prop { (foo: String) => - import Ficus.stringValueReader - import ArbitraryTypeReader._ - implicit val nameMapper: NameMapper = new NameMapper { - override def map(name: String): String = name.toUpperCase - } - - val cfg = ConfigFactory.parseString(s"singleParam { FOO = ${foo.asConfigValue} }") - val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") - instance.getFoo must_== foo - } -} - -object ArbitraryTypeReaderSpec { - trait WithSimpleCompanionApply { - def foo: String - } - - object WithSimpleCompanionApply { - def apply(foo2: String): WithSimpleCompanionApply = new WithSimpleCompanionApply { - val foo = foo2 - } - } - - trait WithMultiCompanionApply { - def foo: String - def bar: Int - } - - object WithMultiCompanionApply { - def apply(foo: String, bar: Int): WithMultiCompanionApply = { - val (_foo, _bar) = (foo, bar) - new WithMultiCompanionApply { - val foo = _foo - val bar = _bar - } - } - } - - trait WithDefault { - def foo: String - } - - object WithDefault { - def apply(foo: String = "defaultFoo"): WithDefault = { - val _foo = foo - new WithDefault { - val foo = _foo - } - } - } - - trait WithOption { - def option: Option[String] - } - - object WithOption { - def apply(option: Option[String]): WithOption = { - val _option = option - new WithOption { - val option = _option - } - } - } - - class ClassWithSingleParam(foo: String) { - def getFoo = foo - } - - class ClassWithMultipleParams(val foo: String, val bar: Int) - - class ClassWithDefault(val foo: String = "defaultFoo") - - trait WithMultipleApplyMethods { - def foo: String - } - - object WithMultipleApplyMethods { - - def apply(foo: Option[String]): Option[WithMultipleApplyMethods] = foo map { f => - new WithMultipleApplyMethods { - def foo: String = f - } - } - - def apply(foo: String): WithMultipleApplyMethods = { - val _foo = foo - new WithMultipleApplyMethods { - def foo: String = _foo - } - } - } - - class ClassWithMultipleConstructors(val foo: String) { - def this(fooInt: Int) = this(fooInt.toString) - } - - case class WithReaderInCompanion(foo: String) - - object WithReaderInCompanion { - implicit val reader: ValueReader[WithReaderInCompanion] = - ValueReader.relative(_ => WithReaderInCompanion("from-companion")) - } - -} +//package net.ceedubs.ficus +//package readers +// +//import com.typesafe.config.ConfigFactory +//import ConfigSerializerOps._ +////import shapeless.test.illTyped +// +//class ArbitraryTypeReaderSpec extends Spec { +// def is = s2""" +// An arbitrary type reader should +// instantiate with a single-param apply method $instantiateSingleParamApply +// instantiate with a single-param apply method from config itself $instantiateSingleParamApplyFromSelf +// instantiate with no apply method but a single constructor with a single param $instantiateSingleParamConstructor +// instantiate with a multi-param apply method $instantiateMultiParamApply +// instantiate with no apply method but a single constructor with multiple params $instantiateMultiParamConstructor +// instantiate with multiple apply methods if only one returns the correct type $multipleApply +// instantiate with primary constructor when no apply methods and multiple constructors $multipleConstructors +// use another implicit value reader for a field $withOptionField +// fall back to a default value on an apply method $fallBackToApplyMethodDefaultValue +// fall back to default values on an apply method if base key isn't in config $fallBackToApplyMethodDefaultValueNoKey +// fall back to a default value on a constructor arg $fallBackToConstructorDefaultValue +// fall back to a default values on a constructor if base key isn't in config $fallBackToConstructorDefaultValueNoKey +// ignore a default value on an apply method if a value is in config $ignoreApplyParamDefault +// ignore a default value in a constructor if a value is in config $ignoreConstructorParamDefault +// allow overriding of option reader for default values $overrideOptionReaderForDefault +// not choose between multiple Java constructors $notChooseBetweenJavaConstructors +// not be prioritized over a Reader defined in a type's companion object (when Ficus._ is imported) $notTrumpCompanionReader +// use name mapper $useNameMapper +// """ +// +// import ArbitraryTypeReaderSpec._ +// +// def instantiateSingleParamApply = prop { (foo2: String) => +// import Ficus.stringValueReader +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") +// val instance: WithSimpleCompanionApply = +// arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg, "simple") +// instance.foo must beEqualTo(foo2) +// } +// +// def instantiateSingleParamApplyFromSelf = prop { (foo2: String) => +// import Ficus.stringValueReader +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") +// val instance: WithSimpleCompanionApply = +// arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg.getConfig("simple"), ".") +// instance.foo must beEqualTo(foo2) +// } +// +// def instantiateSingleParamConstructor = prop { (foo: String) => +// import Ficus.stringValueReader +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s"singleParam { foo = ${foo.asConfigValue} }") +// val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") +// instance.getFoo must beEqualTo(foo) +// } +// +// def instantiateMultiParamApply = prop { (foo: String, bar: Int) => +// import Ficus.{intValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s""" +// |multi { +// | foo = ${foo.asConfigValue} +// | bar = $bar +// |}""".stripMargin) +// val instance: WithMultiCompanionApply = arbitraryTypeValueReader[WithMultiCompanionApply].value.read(cfg, "multi") +// (instance.foo must beEqualTo(foo)) and (instance.bar must beEqualTo(bar)) +// } +// +// def instantiateMultiParamConstructor = prop { (foo: String, bar: Int) => +// import Ficus.{intValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s""" +// |multi { +// | foo = ${foo.asConfigValue} +// | bar = $bar +// |}""".stripMargin) +// val instance: ClassWithMultipleParams = arbitraryTypeValueReader[ClassWithMultipleParams].value.read(cfg, "multi") +// (instance.foo must beEqualTo(foo)) and (instance.bar must beEqualTo(bar)) +// } +// +// def multipleApply = prop { (foo: String) => +// import Ficus.stringValueReader +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s"withMultipleApply { foo = ${foo.asConfigValue} }") +// val instance: WithMultipleApplyMethods = +// arbitraryTypeValueReader[WithMultipleApplyMethods].value.read(cfg, "withMultipleApply") +// instance.foo must beEqualTo(foo) +// } +// +// def multipleConstructors = prop { (foo: String) => +// import Ficus.stringValueReader +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s"withMultipleConstructors { foo = ${foo.asConfigValue} }") +// val instance: ClassWithMultipleConstructors = +// arbitraryTypeValueReader[ClassWithMultipleConstructors].value.read(cfg, "withMultipleConstructors") +// instance.foo must beEqualTo(foo) +// } +// +// def fallBackToApplyMethodDefaultValue = { +// import Ficus.{optionValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString("withDefault { }") +// arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") +// } +// +// def fallBackToApplyMethodDefaultValueNoKey = { +// import Ficus.{optionValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString("") +// arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") +// } +// +// def fallBackToConstructorDefaultValue = { +// import Ficus.{optionValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString("withDefault { }") +// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") +// } +// +// def fallBackToConstructorDefaultValueNoKey = { +// import Ficus.{optionValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString("") +// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") +// } +// +// def withOptionField = { +// import Ficus.{optionValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString("""withOption { option = "here" }""") +// arbitraryTypeValueReader[WithOption].value.read(cfg, "withOption").option must beEqualTo(Some("here")) +// } +// +// def ignoreApplyParamDefault = prop { (foo: String) => +// import Ficus.{optionValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") +// arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo(foo) +// } +// +// def ignoreConstructorParamDefault = prop { (foo: String) => +// import Ficus.{optionValueReader, stringValueReader} +// import ArbitraryTypeReader._ +// val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") +// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo(foo) +// } +// +// def overrideOptionReaderForDefault = { +// import ArbitraryTypeReader._ +// implicit val stringOptionReader: ValueReader[Option[String]] = Ficus.stringValueReader map { s => +// if (s.isEmpty) None else Some(s) +// } +// val cfg = ConfigFactory.parseString("""withDefault { foo = "" }""") +// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") +// } +// +// def notChooseBetweenJavaConstructors = { +//// illTyped("implicitly[ValueReader[String]]") +//// illTyped("implicitly[ValueReader[Long]]") +//// illTyped("implicitly[ValueReader[Int]]") +//// illTyped("implicitly[ValueReader[Float]]") +//// illTyped("implicitly[ValueReader[Double]]") +//// illTyped("implicitly[ValueReader[Char]]") +// success // failure would result in compile error +// } +// +// def notTrumpCompanionReader = { +// import Ficus._ +// val cfg = ConfigFactory.parseString("""withReaderInCompanion { foo = "bar" }""") +// WithReaderInCompanion("from-companion") ==== cfg.as[WithReaderInCompanion]("withReaderInCompanion") +// } +// +// def useNameMapper = prop { (foo: String) => +// import Ficus.stringValueReader +// import ArbitraryTypeReader._ +// implicit val nameMapper: NameMapper = new NameMapper { +// override def map(name: String): String = name.toUpperCase +// } +// +// val cfg = ConfigFactory.parseString(s"singleParam { FOO = ${foo.asConfigValue} }") +// val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") +// instance.getFoo must beEqualTo(foo) +// } +//} +// +//object ArbitraryTypeReaderSpec { +// trait WithSimpleCompanionApply { +// def foo: String +// } +// +// object WithSimpleCompanionApply { +// def apply(foo2: String): WithSimpleCompanionApply = new WithSimpleCompanionApply { +// val foo = foo2 +// } +// } +// +// trait WithMultiCompanionApply { +// def foo: String +// def bar: Int +// } +// +// object WithMultiCompanionApply { +// def apply(foo: String, bar: Int): WithMultiCompanionApply = { +// val (_foo, _bar) = (foo, bar) +// new WithMultiCompanionApply { +// val foo = _foo +// val bar = _bar +// } +// } +// } +// +// trait WithDefault { +// def foo: String +// } +// +// object WithDefault { +// def apply(foo: String = "defaultFoo"): WithDefault = { +// val _foo = foo +// new WithDefault { +// val foo = _foo +// } +// } +// } +// +// trait WithOption { +// def option: Option[String] +// } +// +// object WithOption { +// def apply(option: Option[String]): WithOption = { +// val _option = option +// new WithOption { +// val option = _option +// } +// } +// } +// +// class ClassWithSingleParam(foo: String) { +// def getFoo = foo +// } +// +// class ClassWithMultipleParams(val foo: String, val bar: Int) +// +// class ClassWithDefault(val foo: String = "defaultFoo") +// +// trait WithMultipleApplyMethods { +// def foo: String +// } +// +// object WithMultipleApplyMethods { +// +// def apply(foo: Option[String]): Option[WithMultipleApplyMethods] = foo map { f => +// new WithMultipleApplyMethods { +// def foo: String = f +// } +// } +// +// def apply(foo: String): WithMultipleApplyMethods = { +// val _foo = foo +// new WithMultipleApplyMethods { +// def foo: String = _foo +// } +// } +// } +// +// class ClassWithMultipleConstructors(val foo: String) { +// def this(fooInt: Int) = this(fooInt.toString) +// } +// +// case class WithReaderInCompanion(foo: String) +// +// object WithReaderInCompanion { +// implicit val reader: ValueReader[WithReaderInCompanion] = +// ValueReader.relative(_ => WithReaderInCompanion("from-companion")) +// } +// +//} diff --git a/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala index 855b786..a9748ce 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala @@ -1,157 +1,156 @@ -package net.ceedubs.ficus -package readers - -import scala.language.implicitConversions -import com.typesafe.config.ConfigFactory -import com.typesafe.config.Config -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ -import ConfigSerializerOps._ - -object CaseClassReadersSpec { - case class SimpleCaseClass(bool: Boolean) - case class MultipleFields(string: String, long: Long) - case class WithOption(option: Option[String]) - case class WithNestedCaseClass(simple: SimpleCaseClass) - case class ValueClass(int: Int) extends AnyVal - case class CompanionImplicit(value: Int) - object CompanionImplicit { - implicit val reader: ValueReader[CompanionImplicit] = - implicitly[ValueReader[Int]].map(CompanionImplicit.apply) - } - case class WithNestedCompanionImplicit(value: CompanionImplicit) - - case class WithNestedValueClass(valueClass: ValueClass) - case class WithDefault(string: String = "bar") - case class Foo( - bool: Boolean, - intOpt: Option[Int], - withNestedCaseClass: WithNestedCaseClass, - withNestedValueClass: WithNestedValueClass - ) -} - -class CaseClassReadersSpec extends Spec { - def is = s2""" - A case class reader should - be able to be used implicitly $useImplicitly - hydrate a simple case class $hydrateSimpleCaseClass - hydrate a simple case class from config itself $hydrateSimpleCaseClassFromSelf - hydrate a case class with multiple fields $multipleFields - use another implicit value reader for a field $withOptionField - read a nested case class $withNestedCaseClass - read a top-level value class $topLevelValueClass - read a nested value class $nestedValueClass - fall back to a default value $fallbackToDefault - do a combination of these things $combination - allow providing custom reader in companion $companionImplicitTopLevel - allow providing custom reader in companion $nestedCompanionImplicit - """ - - import CaseClassReadersSpec._ - - def useImplicitly = { - val cfg = ConfigFactory.parseString("simple { bool = false }") - cfg.as[SimpleCaseClass]("simple") must_== SimpleCaseClass(bool = false) - } - - def hydrateSimpleCaseClass = prop { (bool: Boolean) => - val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") - cfg.as[SimpleCaseClass]("simple") must_== SimpleCaseClass(bool = bool) - } - - def hydrateSimpleCaseClassFromSelf = prop { (bool: Boolean) => - val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") - cfg.getConfig("simple").as[SimpleCaseClass] must_== SimpleCaseClass(bool = bool) - } - - def multipleFields = prop { (foo: String, long: Long) => - val cfg = ConfigFactory.parseString(s""" - |multipleFields { - | string = ${foo.asConfigValue} - | long = $long - |} - """.stripMargin) - cfg.as[MultipleFields]("multipleFields") must_== MultipleFields(string = foo, long = long) - } - - def withOptionField = prop { (s: String) => - val cfg = ConfigFactory.parseString(s"""withOption { option = ${s.asConfigValue} }""") - cfg.as[WithOption]("withOption") must_== WithOption(Some(s)) - } - - def withNestedCaseClass = prop { (bool: Boolean) => - val cfg = ConfigFactory.parseString(s""" - |withNested { - | simple { - | bool = $bool - | } - |} - """.stripMargin) - cfg.as[WithNestedCaseClass]("withNested") must_== WithNestedCaseClass(simple = SimpleCaseClass(bool = bool)) - } - - def topLevelValueClass = prop { (int: Int) => - val cfg = ConfigFactory.parseString(s"valueClass { int = $int }") - cfg.as[ValueClass]("valueClass") must_== ValueClass(int) - } - - def nestedValueClass = prop { (int: Int) => - val cfg = ConfigFactory.parseString(s""" - |withNestedValueClass { - | valueClass = { - | int = $int - | } - |} - """.stripMargin) - cfg.as[WithNestedValueClass]("withNestedValueClass") must_== WithNestedValueClass( - valueClass = ValueClass(int = int) - ) - } - - def companionImplicitTopLevel = prop { (int: Int) => - val cfg = ConfigFactory.parseString(s"value = $int ") - cfg.as[CompanionImplicit]("value") must_== CompanionImplicit(int) - } - - def nestedCompanionImplicit = prop { (int: Int) => - val cfg = ConfigFactory.parseString(s""" - |withNestedCompanionImplicit { - | value = $int - |} - """.stripMargin) - cfg.as[WithNestedCompanionImplicit]("withNestedCompanionImplicit") must_== WithNestedCompanionImplicit( - value = CompanionImplicit(value = int) - ) - } - - def fallbackToDefault = { - val cfg = ConfigFactory.parseString("""withDefault { }""") - cfg.as[WithDefault]("withDefault") must_== WithDefault() - } - - def combination = prop { (fooBool: Boolean, simpleBool: Boolean, valueClassInt: Int) => - val cfg = ConfigFactory.parseString(s""" - |foo { - | bool = $fooBool - | withNestedCaseClass { - | simple { - | bool = $simpleBool - | } - | } - | withNestedValueClass = { - | valueClass = { - | int = $valueClassInt - | } - | } - |} - """.stripMargin) - cfg.as[Foo]("foo") must_== Foo( - bool = fooBool, - intOpt = None, - withNestedCaseClass = WithNestedCaseClass(simple = SimpleCaseClass(bool = simpleBool)), - withNestedValueClass = WithNestedValueClass(ValueClass(int = valueClassInt)) - ) - } - -} +//package net.ceedubs.ficus +//package readers +// +//import com.typesafe.config.ConfigFactory +//import com.typesafe.config.Config +//import net.ceedubs.ficus.Ficus._ +//import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +//import ConfigSerializerOps._ +// +//object CaseClassReadersSpec { +// case class SimpleCaseClass(bool: Boolean) +// case class MultipleFields(string: String, long: Long) +// case class WithOption(option: Option[String]) +// case class WithNestedCaseClass(simple: SimpleCaseClass) +// case class ValueClass(int: Int) extends AnyVal +// case class CompanionImplicit(value: Int) +// object CompanionImplicit { +// implicit val reader: ValueReader[CompanionImplicit] = +// implicitly[ValueReader[Int]].map(CompanionImplicit.apply) +// } +// case class WithNestedCompanionImplicit(value: CompanionImplicit) +// +// case class WithNestedValueClass(valueClass: ValueClass) +// case class WithDefault(string: String = "bar") +// case class Foo( +// bool: Boolean, +// intOpt: Option[Int], +// withNestedCaseClass: WithNestedCaseClass, +// withNestedValueClass: WithNestedValueClass +// ) +//} +// +//class CaseClassReadersSpec extends Spec { +// def is = s2""" +// A case class reader should +// be able to be used implicitly $useImplicitly +// hydrate a simple case class $hydrateSimpleCaseClass +// hydrate a simple case class from config itself $hydrateSimpleCaseClassFromSelf +// hydrate a case class with multiple fields $multipleFields +// use another implicit value reader for a field $withOptionField +// read a nested case class $withNestedCaseClass +// read a top-level value class $topLevelValueClass +// read a nested value class $nestedValueClass +// fall back to a default value $fallbackToDefault +// do a combination of these things $combination +// allow providing custom reader in companion $companionImplicitTopLevel +// allow providing custom reader in companion $nestedCompanionImplicit +// """ +// +// import CaseClassReadersSpec._ +// +// def useImplicitly = { +// val cfg = ConfigFactory.parseString("simple { bool = false }") +// cfg.as[SimpleCaseClass]("simple") must beEqualTo(SimpleCaseClass)(bool = false) +// } +// +// def hydrateSimpleCaseClass = prop { (bool: Boolean) => +// val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") +// cfg.as[SimpleCaseClass]("simple") must beEqualTo(SimpleCaseClass)(bool = bool) +// } +// +// def hydrateSimpleCaseClassFromSelf = prop { (bool: Boolean) => +// val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") +// cfg.getConfig("simple").as[SimpleCaseClass] must beEqualTo(SimpleCaseClass)(bool = bool) +// } +// +// def multipleFields = prop { (foo: String, long: Long) => +// val cfg = ConfigFactory.parseString(s""" +// |multipleFields { +// | string = ${foo.asConfigValue} +// | long = $long +// |} +// """.stripMargin) +// cfg.as[MultipleFields]("multipleFields") must beEqualTo(MultipleFields)(string = foo, long = long) +// } +// +// def withOptionField = prop { (s: String) => +// val cfg = ConfigFactory.parseString(s"""withOption { option = ${s.asConfigValue} }""") +// cfg.as[WithOption]("withOption") must beEqualTo(WithOption)(Some(s)) +// } +// +// def withNestedCaseClass = prop { (bool: Boolean) => +// val cfg = ConfigFactory.parseString(s""" +// |withNested { +// | simple { +// | bool = $bool +// | } +// |} +// """.stripMargin) +// cfg.as[WithNestedCaseClass]("withNested") must beEqualTo(WithNestedCaseClass)(simple = SimpleCaseClass(bool = bool)) +// } +// +// def topLevelValueClass = prop { (int: Int) => +// val cfg = ConfigFactory.parseString(s"valueClass { int = $int }") +// cfg.as[ValueClass]("valueClass") must beEqualTo(ValueClass)(int) +// } +// +// def nestedValueClass = prop { (int: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |withNestedValueClass { +// | valueClass = { +// | int = $int +// | } +// |} +// """.stripMargin) +// cfg.as[WithNestedValueClass]("withNestedValueClass") must beEqualTo(WithNestedValueClass)( +// valueClass = ValueClass(int = int) +// ) +// } +// +// def companionImplicitTopLevel = prop { (int: Int) => +// val cfg = ConfigFactory.parseString(s"value = $int ") +// cfg.as[CompanionImplicit]("value") must beEqualTo(CompanionImplicit)(int) +// } +// +// def nestedCompanionImplicit = prop { (int: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |withNestedCompanionImplicit { +// | value = $int +// |} +// """.stripMargin) +// cfg.as[WithNestedCompanionImplicit]("withNestedCompanionImplicit") must beEqualTo(WithNestedCompanionImplicit)( +// value = CompanionImplicit(value = int) +// ) +// } +// +// def fallbackToDefault = { +// val cfg = ConfigFactory.parseString("""withDefault { }""") +// cfg.as[WithDefault]("withDefault") must beEqualTo(WithDefault)() +// } +// +// def combination = prop { (fooBool: Boolean, simpleBool: Boolean, valueClassInt: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |foo { +// | bool = $fooBool +// | withNestedCaseClass { +// | simple { +// | bool = $simpleBool +// | } +// | } +// | withNestedValueClass = { +// | valueClass = { +// | int = $valueClassInt +// | } +// | } +// |} +// """.stripMargin) +// cfg.as[Foo]("foo") must beEqualTo(Foo)( +// bool = fooBool, +// intOpt = None, +// withNestedCaseClass = WithNestedCaseClass(simple = SimpleCaseClass(bool = simpleBool)), +// withNestedValueClass = WithNestedValueClass(ValueClass(int = valueClassInt)) +// ) +// } +// +//} diff --git a/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala index 4526acd..1f15fe5 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala @@ -1,61 +1,61 @@ -package net.ceedubs.ficus -package readers - -import com.typesafe.config.{Config, ConfigFactory} -import Ficus._ - -class ConfigReaderSpec extends Spec { - def is = s2""" - The Config value reader should - read a config $readConfig - implicitly read a config $implicitlyReadConfig - read a ficus config $readFicusConfig - implicitly read a ficus config $implicitlyReadFicusConfig - implicitly read a ficus config itself $implicitlyReadFicusConfigFromSelf - """ - - def readConfig = prop { (i: Int) => - val cfg = ConfigFactory.parseString(s""" - |myConfig { - | myValue = $i - |} - """.stripMargin) - configValueReader.read(cfg, "myConfig").getInt("myValue") must beEqualTo(i) - } - - def implicitlyReadConfig = prop { (i: Int) => - val cfg = ConfigFactory.parseString(s""" - |myConfig { - | myValue = $i - |} - """.stripMargin) - cfg.as[Config]("myConfig").getInt("myValue") must beEqualTo(i) - } - - def readFicusConfig = prop { (i: Int) => - val cfg = ConfigFactory.parseString(s""" - |myConfig { - | myValue = $i - |} - """.stripMargin) - ficusConfigValueReader.read(cfg, "myConfig").as[Int]("myValue") must beEqualTo(i) - } - - def implicitlyReadFicusConfig = prop { (i: Int) => - val cfg = ConfigFactory.parseString(s""" - |myConfig { - | myValue = $i - |} - """.stripMargin) - cfg.as[FicusConfig]("myConfig").as[Int]("myValue") must beEqualTo(i) - } - - def implicitlyReadFicusConfigFromSelf = prop { (i: Int) => - val cfg = ConfigFactory.parseString(s""" - |myConfig { - | myValue = $i - |} - """.stripMargin) - cfg.getConfig("myConfig").as[FicusConfig].as[Int]("myValue") must beEqualTo(i) - } -} +//package net.ceedubs.ficus +//package readers +// +//import com.typesafe.config.{Config, ConfigFactory} +//import Ficus._ +// +//class ConfigReaderSpec extends Spec { +// def is = s2""" +// The Config value reader should +// read a config $readConfig +// implicitly read a config $implicitlyReadConfig +// read a ficus config $readFicusConfig +// implicitly read a ficus config $implicitlyReadFicusConfig +// implicitly read a ficus config itself $implicitlyReadFicusConfigFromSelf +// """ +// +// def readConfig = prop { (i: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |myConfig { +// | myValue = $i +// |} +// """.stripMargin) +// configValueReader.read(cfg, "myConfig").getInt("myValue") must beEqualTo(i) +// } +// +// def implicitlyReadConfig = prop { (i: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |myConfig { +// | myValue = $i +// |} +// """.stripMargin) +// cfg.as[Config]("myConfig").getInt("myValue") must beEqualTo(i) +// } +// +// def readFicusConfig = prop { (i: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |myConfig { +// | myValue = $i +// |} +// """.stripMargin) +// ficusConfigValueReader.read(cfg, "myConfig").as[Int]("myValue") must beEqualTo(i) +// } +// +// def implicitlyReadFicusConfig = prop { (i: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |myConfig { +// | myValue = $i +// |} +// """.stripMargin) +// cfg.as[FicusConfig]("myConfig").as[Int]("myValue") must beEqualTo(i) +// } +// +// def implicitlyReadFicusConfigFromSelf = prop { (i: Int) => +// val cfg = ConfigFactory.parseString(s""" +// |myConfig { +// | myValue = $i +// |} +// """.stripMargin) +// cfg.getConfig("myConfig").as[FicusConfig].as[Int]("myValue") must beEqualTo(i) +// } +//} diff --git a/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala index c17508b..1cf7ecf 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala @@ -1,55 +1,55 @@ -package net.ceedubs.ficus -package readers - -import com.typesafe.config.ConfigFactory - -import scala.concurrent.duration._ -import org.scalacheck.{Gen, Prop} - -class DurationReadersSpec extends Spec with DurationReaders { - def is = s2""" - The finite duration reader should - read a millisecond value ${readMillis(finiteDurationReader)} - read a minute value ${readMinutes(finiteDurationReader)} - read a days value into days ${readDaysUnit(finiteDurationReader)} - - The duration reader should - read a millisecond value ${readMillis(durationReader)} - read a minute value ${readMinutes(durationReader)} - read a days value into days ${readDaysUnit(durationReader)} - read positive infinite values $readPositiveInf - read negative infinite values $readNegativeInf - """ - - def readMillis[T](reader: ValueReader[T]) = prop { (i: Int) => - val cfg = ConfigFactory.parseString(s"myValue = $i") - reader.read(cfg, "myValue") must beEqualTo(i millis) - } - - def readMinutes[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-1.5e8.toInt, 1.5e8.toInt)) { (i: Int) => - val cfg = ConfigFactory.parseString("myValue = \"" + i + " minutes\"") - reader.read(cfg, "myValue") must beEqualTo(i minutes) - } - - def readDaysUnit[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-106580, 106580)) { (i: Int) => - val str = i.toString + " day" + (if (i == 1) "" else "s") - val cfg = ConfigFactory.parseString(s"""myValue = "$str" """) - reader.read(cfg, "myValue").toString must beEqualTo(str) - } - - def readPositiveInf = { - val positiveInf = List("Inf", "PlusInf", "\"+Inf\"") - positiveInf.forall { (s: String) => - val cfg = ConfigFactory.parseString(s"myValue = $s") - durationReader.read(cfg, "myValue") should be(Duration.Inf) - } - } - - def readNegativeInf = { - val negativeInf = List("-Inf", "MinusInf") - negativeInf.forall { (s: String) => - val cfg = ConfigFactory.parseString(s"myValue = $s") - durationReader.read(cfg, "myValue") should be(Duration.MinusInf) - } - } -} +//package net.ceedubs.ficus +//package readers +// +//import com.typesafe.config.ConfigFactory +// +//import scala.concurrent.duration._ +//import org.scalacheck.{Gen, Prop} +// +//class DurationReadersSpec extends Spec with DurationReaders { +// def is = s2""" +// The finite duration reader should +// read a millisecond value ${readMillis(finiteDurationReader)} +// read a minute value ${readMinutes(finiteDurationReader)} +// read a days value into days ${readDaysUnit(finiteDurationReader)} +// +// The duration reader should +// read a millisecond value ${readMillis(durationReader)} +// read a minute value ${readMinutes(durationReader)} +// read a days value into days ${readDaysUnit(durationReader)} +// read positive infinite values $readPositiveInf +// read negative infinite values $readNegativeInf +// """ +// +// def readMillis[T](reader: ValueReader[T]) = prop { (i: Int) => +// val cfg = ConfigFactory.parseString(s"myValue = $i") +// reader.read(cfg, "myValue") must beEqualTo(i millis) +// } +// +// def readMinutes[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-1.5e8.toInt, 1.5e8.toInt)) { (i: Int) => +// val cfg = ConfigFactory.parseString("myValue = \"" + i + " minutes\"") +// reader.read(cfg, "myValue") must beEqualTo(i minutes) +// } +// +// def readDaysUnit[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-106580, 106580)) { (i: Int) => +// val str = i.toString + " day" + (if (i == 1) "" else "s") +// val cfg = ConfigFactory.parseString(s"""myValue = "$str" """) +// reader.read(cfg, "myValue").toString must beEqualTo(str) +// } +// +// def readPositiveInf = { +// val positiveInf = List("Inf", "PlusInf", "\"+Inf\"") +// positiveInf.forall { (s: String) => +// val cfg = ConfigFactory.parseString(s"myValue = $s") +// durationReader.read(cfg, "myValue") should be(Duration.Inf) +// } +// } +// +// def readNegativeInf = { +// val negativeInf = List("-Inf", "MinusInf") +// negativeInf.forall { (s: String) => +// val cfg = ConfigFactory.parseString(s"myValue = $s") +// durationReader.read(cfg, "myValue") should be(Duration.MinusInf) +// } +// } +//} diff --git a/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala index 1783340..fa093da 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/HyphenNameMapperSpec.scala @@ -21,7 +21,7 @@ class HyphenNameMapperSpec extends Spec with DataTables { val camelCased = (foos.head +: foos.tail.map(_.capitalize)).mkString val hyphenated = foos.mkString("-").toLowerCase - HyphenNameMapper.map(camelCased) must_== hyphenated + HyphenNameMapper.map(camelCased) must beEqualTo(hyphenated) } def hyphenateWithDigits = @@ -30,6 +30,6 @@ class HyphenNameMapperSpec extends Spec with DataTables { "1144StartsWithA32422" !! "1144-starts-with-a-32422" | "get13HTML42Snippets" !! "get-13-html-42-snippets" | "thisOneIs13InThe43Middle" !! "this-one-is-13-in-the-43-middle" | { (camelCased, hyphenated) => - HyphenNameMapper.map(camelCased) must_== hyphenated + HyphenNameMapper.map(camelCased) must beEqualTo(hyphenated) } } diff --git a/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala index 26a6a08..de0308e 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala @@ -5,7 +5,7 @@ import java.time.{ZoneId, ZonedDateTime} import com.typesafe.config.ConfigFactory -import Ficus.{toFicusConfig, isoZonedDateTimeReader} +import Ficus._ class ISOZonedDateTimeReaderSpec extends Spec { def is = s2""" @@ -19,7 +19,7 @@ class ISOZonedDateTimeReaderSpec extends Spec { | date = "2016-02-28T11:46:26.896+01:00[Europe/Berlin]" | } """.stripMargin) - val date = cfg.as[ZonedDateTime]("foo.date") + val date = cfg.to[ZonedDateTime]("foo.date") val expected = ZonedDateTime.of( 2016, 2, @@ -30,6 +30,6 @@ class ISOZonedDateTimeReaderSpec extends Spec { 896000000, ZoneId.of("Europe/Berlin") ) - date should_== expected + date should beEqualTo(expected) } } diff --git a/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala index f472439..8ea7a8d 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala @@ -3,11 +3,11 @@ package readers import java.time.LocalDate import com.typesafe.config.ConfigFactory -import Ficus.{toFicusConfig, localDateReader} +import Ficus._ class LocalDateReaderSpec extends Spec { def is = s2""" - The LocalDateReader should + The LocalDateReader should read a LocalDate in ISO format without a time-zone: $readLocalDate """ @@ -17,8 +17,8 @@ class LocalDateReaderSpec extends Spec { | date = "2003-01-03" | } """.stripMargin) - val localDate = cfg.as[LocalDate]("foo.date") + val localDate = cfg.to[LocalDate]("foo.date") val expected = LocalDate.of(2003, 1, 3) - localDate should_== expected + localDate should beEqualTo(expected) } } diff --git a/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala index 33e6aa8..32bf4b4 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala @@ -2,10 +2,11 @@ package net.ceedubs.ficus package readers import java.time.Period -import com.typesafe.config.ConfigFactory -import Ficus.{toFicusConfig, periodReader} +import com.typesafe.config.{ Config, ConfigFactory } +import Ficus._ class PeriodReaderSpec extends Spec { + val periodReaderExists = implicitly[ValueReader[Period]] def is = s2""" The PeriodReader should read a Period in ISO-8601 format $readPeriod @@ -13,14 +14,14 @@ class PeriodReaderSpec extends Spec { """ def readPeriod = { - val cfg = ConfigFactory.parseString(s""" + val cfg: Config = ConfigFactory.parseString(s""" | foo { | interval = "P1Y3M10D" | } """.stripMargin) - val period = cfg.as[Period]("foo.interval") + val period: Period = cfg.to("foo.interval") val expected = Period.of(1, 3, 10) - period should_== expected + period should beEqualTo(expected) } def readNegativePeriod = { @@ -29,8 +30,8 @@ class PeriodReaderSpec extends Spec { | interval = "P-1Y10M3D" | } """.stripMargin) - val period = cfg.as[Period]("foo.interval") + val period = cfg.to[Period]("foo.interval") val expected = Period.of(-1, 10, 3) - period should_== expected + period should beEqualTo(expected) } } diff --git a/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala index c9a5ce6..51dc503 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala @@ -1,28 +1,28 @@ -package net.ceedubs.ficus.readers - -import java.net.URL - -import com.typesafe.config.ConfigException.WrongType -import com.typesafe.config.ConfigFactory -import net.ceedubs.ficus.Spec -import org.specs2.matcher.MatchResult - -class URLReaderSpec extends Spec with URLReader with TryReader { - def is = s2""" - The URL value reader should - read a valid URL $readValidURL - detect wrong type on malformed URL (with an unsupported protocol) $readMalformedURL - """ - - def readValidURL: MatchResult[URL] = { - val url = """https://www.google.com""" - val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + url + "\""}") - javaURLReader.read(cfg, "myValue") must beEqualTo(new URL(url)) - } - - def readMalformedURL: MatchResult[Any] = { - val malformedUrl = """foo://bar.com""" - val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + malformedUrl + "\""}") - javaURLReader.read(cfg, "myValue") must throwA[WrongType] - } -} +//package net.ceedubs.ficus.readers +// +//import java.net.URL +// +//import com.typesafe.config.ConfigException.WrongType +//import com.typesafe.config.ConfigFactory +//import net.ceedubs.ficus.Spec +//import org.specs2.matcher.MatchResult +// +//class URLReaderSpec extends Spec with URLReader with TryReader { +// def is = s2""" +// The URL value reader should +// read a valid URL $readValidURL +// detect wrong type on malformed URL (with an unsupported protocol) $readMalformedURL +// """ +// +// def readValidURL: MatchResult[URL] = { +// val url = """https://www.google.com""" +// val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + url + "\""}") +// javaURLReader.read(cfg, "myValue") must beEqualTo(new URL(url)) +// } +// +// def readMalformedURL: MatchResult[Any] = { +// val malformedUrl = """foo://bar.com""" +// val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + malformedUrl + "\""}") +// javaURLReader.read(cfg, "myValue") must throwA[WrongType] +// } +//} From e9686ddc8b060e7de10ec993d01a33b2498ef282 Mon Sep 17 00:00:00 2001 From: Hugh Simpson Date: Mon, 2 Aug 2021 16:42:28 +0100 Subject: [PATCH 3/3] lol wip --- .scalafmt.conf | 7 +- build.sbt | 3 +- .../net/ceedubs/ficus/FicusConfig.scala | 4 +- .../scala-3/net/ceedubs/ficus/Ficus.scala | 13 +- .../net/ceedubs/ficus/FicusConfig.scala | 7 +- .../ficus/readers/ArbitraryTypeReader.scala | 79 ++- .../ficus/readers/CollectionReaders.scala | 2 +- .../ficus/readers/EnumerationReader.scala | 6 +- .../ficus/readers/DurationReaders.scala | 15 +- .../ceedubs/ficus/readers/NameMapper.scala | 13 +- .../ceedubs/ficus/readers/ValueReader.scala | 3 +- .../net/ceedubs/ficus/Issue82Spec.scala | 2 +- .../readers/EnumerationReadersSpec.scala | 2 +- .../net/ceedubs/ficus/ConfigSerializer.scala | 4 +- .../scala/net/ceedubs/ficus/Examples.scala | 14 +- .../readers/ArbitraryTypeReaderSpec.scala | 558 +++++++++--------- .../ficus/readers/CaseClassReadersSpec.scala | 310 +++++----- .../ficus/readers/ConfigReaderSpec.scala | 122 ++-- .../ficus/readers/DurationReadersSpec.scala | 111 ++-- .../readers/ISOZonedDateTimeReaderSpec.scala | 7 +- .../ficus/readers/LocalDateReaderSpec.scala | 6 +- .../ficus/readers/PeriodReaderSpec.scala | 16 +- .../ceedubs/ficus/readers/URLReaderSpec.scala | 56 +- 23 files changed, 717 insertions(+), 643 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 0710cb2..0de6d42 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 2.7.5 +version = 3.0.0-RC6 preset = default align.preset = most maxColumn = 120 @@ -7,3 +7,8 @@ align.tokens.add = [ {code = ":=", owner = "Term.ApplyInfix"} ] rewrite.rules = [RedundantBraces, RedundantParens] +fileOverride { + "glob:**/src/main/scala-3/**" { + runner.dialect = scala3 + } +} \ No newline at end of file diff --git a/build.sbt b/build.sbt index 76e4312..19a4145 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,7 @@ ThisBuild / githubWorkflowUseSbtThinClient := false ThisBuild / githubWorkflowPublishTargetBranches := Seq() ThisBuild / crossScalaVersions := Seq("2.10.7", "2.11.12", "2.13.6", "3.0.1", "2.12.14") -ThisBuild / scalaVersion := (ThisBuild / crossScalaVersions).value.last +ThisBuild / scalaVersion := "3.0.2-RC1" // Coveralls doesn't really work with Scala 2.10.7 so we are disabling it for CI ThisBuild / githubWorkflowScalaVersions -= "2.10.7" @@ -43,6 +43,7 @@ lazy val root = project description := "A Scala-friendly wrapper companion for Typesafe config", startYear := Some(2013), scalacOptions ++= Seq( + "-language:implicitConversions", "-feature", "-deprecation", "-unchecked", diff --git a/src/main/scala-2/net/ceedubs/ficus/FicusConfig.scala b/src/main/scala-2/net/ceedubs/ficus/FicusConfig.scala index 7d8ad12..31b05e6 100644 --- a/src/main/scala-2/net/ceedubs/ficus/FicusConfig.scala +++ b/src/main/scala-2/net/ceedubs/ficus/FicusConfig.scala @@ -6,9 +6,11 @@ import net.ceedubs.ficus.readers.{AllValueReaderInstances, ValueReader} trait FicusConfig { def config: Config + def self: FicusConfig = this + def as[A](path: String)(implicit reader: ValueReader[A]): A = reader.read(config, path) - def as[A](implicit reader: ValueReader[A]): A = as(".") + def to[A](implicit reader: ValueReader[A]): A = as(".") def getAs[A](path: String)(implicit reader: ValueReader[Option[A]]): Option[A] = reader.read(config, path) diff --git a/src/main/scala-3/net/ceedubs/ficus/Ficus.scala b/src/main/scala-3/net/ceedubs/ficus/Ficus.scala index 1ca9cba..9e36538 100644 --- a/src/main/scala-3/net/ceedubs/ficus/Ficus.scala +++ b/src/main/scala-3/net/ceedubs/ficus/Ficus.scala @@ -22,16 +22,5 @@ trait FicusInstances with InetSocketAddressReaders object Ficus extends FicusInstances { - extension (config: Config) { - def to[A](path: String)(implicit reader: ValueReader[A]): A = reader.read(config, path) - - def as[A](implicit reader: ValueReader[A]): A = to(".") - - def getAs[A](path: String)(implicit reader: ValueReader[Option[A]]): Option[A] = reader.read(config, path) - - def getOrElse[A](path: String, default: => A)(implicit reader: ValueReader[Option[A]]): A = - getAs[A](path).getOrElse(default) - - def apply[A](key: ConfigKey[A])(implicit reader: ValueReader[A]): A = to[A](key.path) - } + implicit def toFicusConfig(config: Config): FicusConfig = SimpleFicusConfig(config) } diff --git a/src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala b/src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala index 4e72dde..a07e3fc 100644 --- a/src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala +++ b/src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala @@ -6,9 +6,11 @@ import net.ceedubs.ficus.readers.{AllValueReaderInstances, ValueReader} trait FicusConfig { def config: Config - def as[A](path: String)(implicit reader: ValueReader[A]): A = reader.read(config, path) + def self: FicusConfig = this - def as[A](implicit reader: ValueReader[A]): A = as(".") + def as[A](path: String)(using reader: ValueReader[A]): A = reader.read(config, path) + + def to[A](using ValueReader[A]): A = as[A](".") def getAs[A](path: String)(implicit reader: ValueReader[Option[A]]): Option[A] = reader.read(config, path) @@ -19,4 +21,3 @@ trait FicusConfig { } final case class SimpleFicusConfig(config: Config) extends FicusConfig - diff --git a/src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala b/src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala index fb274ea..c012b45 100644 --- a/src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala +++ b/src/main/scala-3/net/ceedubs/ficus/readers/ArbitraryTypeReader.scala @@ -1,7 +1,82 @@ package net.ceedubs.ficus.readers +import scala.quoted.* + +import com.typesafe.config.Config + trait ArbitraryTypeReader { - implicit def arbitraryTypeValueReader[T]: Generated[ValueReader[T]] = ??? + implicit inline def arbitraryTypeValueReader[T]: Generated[ValueReader[T]] = + ${ ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T] } } -object ArbitraryTypeReader extends ArbitraryTypeReader \ No newline at end of file +object ArbitraryTypeReader extends ArbitraryTypeReader + +object ArbitraryTypeReaderMacros { + def arbitraryTypeValueReader[T](using Quotes, Type[T]): Expr[Generated[ValueReader[T]]] = { + import quotes.reflect.* + '{ + Generated(new ValueReader[T] { + def read(config: Config, path: String): T = ${ + instantiateFromConfig[T](config = '{ config }, path = '{ path }, mapper = '{ NameMapper() }) + } + }) + } + } + + def instantiateFromConfig[T]( + config: Expr[Config], + path: Expr[String], + mapper: Expr[NameMapper], + default: Option[Expr[T]] = None + )(using Quotes, Type[T]): Expr[T] = { + import quotes.reflect.* + val tTypeTree = TypeTree.of[T] + val typeSymbol: Symbol = tTypeTree.symbol + val isCase = typeSymbol.flags.is(Flags.Case) + val hasCompanion = typeSymbol.companionClass != Symbol.noSymbol && typeSymbol.companionClass.isDefinedInCurrentRun + def getApplyO(companion: Symbol): Option[DefDef] = + companion.memberMethod("apply").filter(_.isDefDef).map(_.tree).collect{ case d: DefDef => d }.find(_.returnTpt.symbol == tTypeTree.symbol) + val companionHasApply = hasCompanion && getApplyO(typeSymbol.companionClass).isDefined + def defaultIfNoPath(expr: Expr[T]): Expr[T] = default match { + case Some(d) => '{ if (! $config.hasPath($path)) $d else $expr } + case None => expr + } +// val isClassDef = typeSymbol.isClassDef + + if (companionHasApply) { + val classDef: ClassDef = typeSymbol.tree.asInstanceOf[ClassDef] + val companionApply: DefDef = getApplyO(typeSymbol.companionClass).get + println(s"companionApply.returnTpt = ${companionApply.returnTpt}") + println(s"companionApply.returnTpt.symbol = ${companionApply.returnTpt.symbol}") + println(s"companionApply.returnTpt.tpe = ${companionApply.returnTpt.tpe}") + println(s"tTypeTree.symbol = ${tTypeTree.symbol}") + println(s"tTypeTree.symbol == companionApply.returnTpt.symbol ${tTypeTree.symbol == companionApply.returnTpt.symbol}") +// println(s"companionApply.returnTpt = ${companionApply.returnTpt.tpe.symbol}") + val companionApplySymbol: Symbol = companionApply.symbol + val params: List[TermParamClause] = companionApply.termParamss + val filledParams: List[Term] = params.head.params.map { case v: ValDef => + val nextPath: Expr[String] = '{ (if ($path == ".") "" else $path + ".") + $mapper.map(${ Expr(v.name) }) } +// v.asExpr match { case '{ $expr: t } => instantiateFromConfig[t](config, nextPath, mapper)(using summon[Quotes], v.tpt.tpe.asType.asInstanceOf[Type[t]]).asTerm } + type X + val tpe = v.tpt.tpe.asType.asInstanceOf[Type[X]] + val default: Option[Expr[X]] = v.rhs.map(_.asExpr.asInstanceOf[Expr[X]]) + if (default.isDefined) println(s"Have a default of $default") + instantiateFromConfig[X](config, nextPath, mapper, default)(using summon[Quotes], tpe) + .asInstanceOf[Expr[Any]] + .asTerm + } + val res = Apply(Ref(companionApplySymbol), filledParams) + defaultIfNoPath(res.asExpr.asInstanceOf[Expr[T]]) + } else { + val leafReader: Expr[ValueReader[T]] = Implicits.search(TypeTree.of[ValueReader[T]].tpe) match { + case succ: ImplicitSearchSuccess => +// report.throwError(s"Actually succeeded, but could recurse...\n tree=${succ.tree}\nsymbol=${succ.tree.symbol}\nexpr=${succ.tree.asExpr.show}") + succ.tree.asExpr.asInstanceOf[Expr[ValueReader[T]]] +// case succ: ImplicitSearchSuccess => Ref(succ.tree.symbol).asExpr.asInstanceOf[Expr[ValueReader[T]]] + case fail: ImplicitSearchFailure => report.throwError(fail.explanation) + } + defaultIfNoPath('{ $leafReader.read($config, $path) }) + } + + } +} diff --git a/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala b/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala index 464c41d..2dc7c46 100644 --- a/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala +++ b/src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala @@ -16,7 +16,7 @@ trait CollectionReaders { factory: Factory[A, C[A]] ): ValueReader[C[A]] = new ValueReader[C[A]] { def read(config: Config, path: String): C[A] = { - val list = config.getList(path).asScala + val list = config.getList(path).asScala val builder: Builder[A, C[A]] = factory.newBuilder builder.sizeHint(list.size) list foreach { entry => diff --git a/src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala b/src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala index 9406638..702881d 100644 --- a/src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala +++ b/src/main/scala-3/net/ceedubs/ficus/readers/EnumerationReader.scala @@ -1,7 +1,5 @@ package net.ceedubs.ficus.readers -trait EnumerationReader { +trait EnumerationReader {} -} - -object EnumerationReader extends EnumerationReader \ No newline at end of file +object EnumerationReader extends EnumerationReader diff --git a/src/main/scala/net/ceedubs/ficus/readers/DurationReaders.scala b/src/main/scala/net/ceedubs/ficus/readers/DurationReaders.scala index 7b12d00..5e97514 100644 --- a/src/main/scala/net/ceedubs/ficus/readers/DurationReaders.scala +++ b/src/main/scala/net/ceedubs/ficus/readers/DurationReaders.scala @@ -8,8 +8,8 @@ import scala.util.Try trait DurationReaders { /** A reader for for a scala.concurrent.duration.FiniteDuration. This reader should be able to read any valid duration - * format as defined by the HOCON spec. - * For example, it can read "15 minutes" or "1 day". + * format as defined by the HOCON spec. For + * example, it can read "15 minutes" or "1 day". */ implicit def finiteDurationReader: ValueReader[FiniteDuration] = new ValueReader[FiniteDuration] { def read(config: Config, path: String): FiniteDuration = { @@ -18,11 +18,12 @@ trait DurationReaders { } } - /** A reader for for a scala.concurrent.duration.Duration. This reader should be able to read any valid duration format - * as defined by the HOCON spec and positive - * and negative infinite values supported by Duration's apply method. - * For example, it can read "15 minutes", "1 day", "-Inf", or "PlusInf". + /** A reader for for a scala.concurrent.duration.Duration. This reader should be able to read any valid duration + * format as defined by the HOCON spec and + * positive and negative infinite values supported by Duration's apply method. For + * example, it can read "15 minutes", "1 day", "-Inf", or "PlusInf". */ implicit def durationReader: ValueReader[Duration] = new ValueReader[Duration] { def read(config: Config, path: String): Duration = diff --git a/src/main/scala/net/ceedubs/ficus/readers/NameMapper.scala b/src/main/scala/net/ceedubs/ficus/readers/NameMapper.scala index 1be1a4c..2fae18f 100644 --- a/src/main/scala/net/ceedubs/ficus/readers/NameMapper.scala +++ b/src/main/scala/net/ceedubs/ficus/readers/NameMapper.scala @@ -1,12 +1,13 @@ package net.ceedubs.ficus.readers -/** Defines an object that knows to map between names as they found in the code - * to those who should be defined in the configuration +/** Defines an object that knows to map between names as they found in the code to those who should be defined in the + * configuration */ trait NameMapper { /** Maps between the name in the code to name in configuration - * @param name The name as found in the code + * @param name + * The name as found in the code */ def map(name: String): String @@ -17,8 +18,10 @@ trait NameMapper { object NameMapper { /** Gets the name mapper from the implicit scope - * @param nameMapper The name mapper from the implicit scope, or the default name mapper if not found - * @return The name mapper to be used in current implicit scope + * @param nameMapper + * The name mapper from the implicit scope, or the default name mapper if not found + * @return + * The name mapper to be used in current implicit scope */ def apply()(implicit nameMapper: NameMapper = DefaultNameMapper): NameMapper = nameMapper diff --git a/src/main/scala/net/ceedubs/ficus/readers/ValueReader.scala b/src/main/scala/net/ceedubs/ficus/readers/ValueReader.scala index a85f306..c8ff58c 100644 --- a/src/main/scala/net/ceedubs/ficus/readers/ValueReader.scala +++ b/src/main/scala/net/ceedubs/ficus/readers/ValueReader.scala @@ -19,8 +19,7 @@ trait ValueReader[A] { self => object ValueReader { implicit def generatedReader[A](implicit generated: Generated[ValueReader[A]]): ValueReader[A] = generated.value - /** Returns the implicit ValueReader[A] in scope. - * `ValueReader[A]` is equivalent to `implicitly[ValueReader[A]]` + /** Returns the implicit ValueReader[A] in scope. `ValueReader[A]` is equivalent to `implicitly[ValueReader[A]]` */ def apply[A](implicit reader: ValueReader[A]): ValueReader[A] = reader diff --git a/src/test/scala-2.13+/net/ceedubs/ficus/Issue82Spec.scala b/src/test/scala-2.13+/net/ceedubs/ficus/Issue82Spec.scala index 1ee6c20..ca9e1bf 100644 --- a/src/test/scala-2.13+/net/ceedubs/ficus/Issue82Spec.scala +++ b/src/test/scala-2.13+/net/ceedubs/ficus/Issue82Spec.scala @@ -10,7 +10,7 @@ class Issue82Spec extends Specification { "not throw `java.lang.ClassCastException`" in { case class TestSettings(val `foo-bar`: Long, `foo`: String) val config = ConfigFactory.parseString("""{ foo-bar: 3, foo: "4" }""") - config.as[TestSettings] must not(throwA[java.lang.ClassCastException]) + config.to[TestSettings] must not(throwA[java.lang.ClassCastException]) } """should not assign "foo-bar" to "foo"""" in { diff --git a/src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala b/src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala index e9c83a2..5a2987b 100644 --- a/src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala +++ b/src/test/scala-3/net/ceedubs/ficus/readers/EnumerationReadersSpec.scala @@ -62,4 +62,4 @@ // } // // enum NotObject -//} \ No newline at end of file +//} diff --git a/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala b/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala index 3bbf541..3d93a0f 100644 --- a/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala +++ b/src/test/scala/net/ceedubs/ficus/ConfigSerializer.scala @@ -34,7 +34,9 @@ object ConfigSerializer { } def iterableSerializer[A: ConfigSerializer]: ConfigSerializer[Iterable[A]] = apply[Iterable[A]](serializeIterable) - implicit def stringKeyMapSerializer[A](implicit valueSerializer: ConfigSerializer[A]): ConfigSerializer[Map[String, A]] = + implicit def stringKeyMapSerializer[A](implicit + valueSerializer: ConfigSerializer[A] + ): ConfigSerializer[Map[String, A]] = new ConfigSerializer[Map[String, A]] { def serialize(map: Map[String, A]): String = { val lines = map.toIterable.map( diff --git a/src/test/scala/net/ceedubs/ficus/Examples.scala b/src/test/scala/net/ceedubs/ficus/Examples.scala index 565a756..14ea8db 100644 --- a/src/test/scala/net/ceedubs/ficus/Examples.scala +++ b/src/test/scala/net/ceedubs/ficus/Examples.scala @@ -11,19 +11,19 @@ class Examples { val config: Config = ConfigFactory.load() // standard Typesafe Config // Note: explicit typing isn't necessary. It's just in these examples to make it clear what the return types are. - // This line could instead be: val appName = config.to[String]("app.name") - val appName: String = config.to[String]("app.name") // equivalent to config.getString("app.name") + // This line could instead be: val appName = config.as[String]("app.name") + val appName: String = config.as[String]("app.name") // equivalent to config.getString("app.name") - // config.to[Option[Boolean]]("preloadCache") will return None if preloadCache isn't defined in the config - val preloadCache: Boolean = config.to[Option[Boolean]]("preloadCache").getOrElse(false) + // config.as[Option[Boolean]]("preloadCache") will return None if preloadCache isn't defined in the config + val preloadCache: Boolean = config.as[Option[Boolean]]("preloadCache").getOrElse(false) - val adminUserIds: Set[Long] = config.to[Set[Long]]("adminIds") + val adminUserIds: Set[Long] = config.as[Set[Long]]("adminIds") // something such to "15 minutes" can be converted to a FiniteDuration - val retryInterval: FiniteDuration = config.to[FiniteDuration]("retryInterval") + val retryInterval: FiniteDuration = config.as[FiniteDuration]("retryInterval") // can hydrate most arbitrary types // it first tries to use an apply method on the companion object and falls back to the primary constructor // if values are not in the config, they will fall back to the default value on the class/apply method - val someCaseClass: SomeCaseClass = config.to[SomeCaseClass]("someCaseClass") + val someCaseClass: SomeCaseClass = config.as[SomeCaseClass]("someCaseClass") } diff --git a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala index d412be3..a76327b 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ArbitraryTypeReaderSpec.scala @@ -1,279 +1,279 @@ -//package net.ceedubs.ficus -//package readers -// -//import com.typesafe.config.ConfigFactory -//import ConfigSerializerOps._ -////import shapeless.test.illTyped -// -//class ArbitraryTypeReaderSpec extends Spec { -// def is = s2""" -// An arbitrary type reader should -// instantiate with a single-param apply method $instantiateSingleParamApply -// instantiate with a single-param apply method from config itself $instantiateSingleParamApplyFromSelf -// instantiate with no apply method but a single constructor with a single param $instantiateSingleParamConstructor -// instantiate with a multi-param apply method $instantiateMultiParamApply -// instantiate with no apply method but a single constructor with multiple params $instantiateMultiParamConstructor -// instantiate with multiple apply methods if only one returns the correct type $multipleApply -// instantiate with primary constructor when no apply methods and multiple constructors $multipleConstructors -// use another implicit value reader for a field $withOptionField -// fall back to a default value on an apply method $fallBackToApplyMethodDefaultValue -// fall back to default values on an apply method if base key isn't in config $fallBackToApplyMethodDefaultValueNoKey -// fall back to a default value on a constructor arg $fallBackToConstructorDefaultValue -// fall back to a default values on a constructor if base key isn't in config $fallBackToConstructorDefaultValueNoKey -// ignore a default value on an apply method if a value is in config $ignoreApplyParamDefault -// ignore a default value in a constructor if a value is in config $ignoreConstructorParamDefault -// allow overriding of option reader for default values $overrideOptionReaderForDefault -// not choose between multiple Java constructors $notChooseBetweenJavaConstructors -// not be prioritized over a Reader defined in a type's companion object (when Ficus._ is imported) $notTrumpCompanionReader -// use name mapper $useNameMapper -// """ -// -// import ArbitraryTypeReaderSpec._ -// -// def instantiateSingleParamApply = prop { (foo2: String) => -// import Ficus.stringValueReader -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") -// val instance: WithSimpleCompanionApply = -// arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg, "simple") -// instance.foo must beEqualTo(foo2) -// } -// -// def instantiateSingleParamApplyFromSelf = prop { (foo2: String) => -// import Ficus.stringValueReader -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") -// val instance: WithSimpleCompanionApply = -// arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg.getConfig("simple"), ".") -// instance.foo must beEqualTo(foo2) -// } -// -// def instantiateSingleParamConstructor = prop { (foo: String) => -// import Ficus.stringValueReader -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s"singleParam { foo = ${foo.asConfigValue} }") -// val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") -// instance.getFoo must beEqualTo(foo) -// } -// -// def instantiateMultiParamApply = prop { (foo: String, bar: Int) => -// import Ficus.{intValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s""" -// |multi { -// | foo = ${foo.asConfigValue} -// | bar = $bar -// |}""".stripMargin) -// val instance: WithMultiCompanionApply = arbitraryTypeValueReader[WithMultiCompanionApply].value.read(cfg, "multi") -// (instance.foo must beEqualTo(foo)) and (instance.bar must beEqualTo(bar)) -// } -// -// def instantiateMultiParamConstructor = prop { (foo: String, bar: Int) => -// import Ficus.{intValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s""" -// |multi { -// | foo = ${foo.asConfigValue} -// | bar = $bar -// |}""".stripMargin) -// val instance: ClassWithMultipleParams = arbitraryTypeValueReader[ClassWithMultipleParams].value.read(cfg, "multi") -// (instance.foo must beEqualTo(foo)) and (instance.bar must beEqualTo(bar)) -// } -// -// def multipleApply = prop { (foo: String) => -// import Ficus.stringValueReader -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s"withMultipleApply { foo = ${foo.asConfigValue} }") -// val instance: WithMultipleApplyMethods = -// arbitraryTypeValueReader[WithMultipleApplyMethods].value.read(cfg, "withMultipleApply") -// instance.foo must beEqualTo(foo) -// } -// -// def multipleConstructors = prop { (foo: String) => -// import Ficus.stringValueReader -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s"withMultipleConstructors { foo = ${foo.asConfigValue} }") -// val instance: ClassWithMultipleConstructors = -// arbitraryTypeValueReader[ClassWithMultipleConstructors].value.read(cfg, "withMultipleConstructors") -// instance.foo must beEqualTo(foo) -// } -// -// def fallBackToApplyMethodDefaultValue = { -// import Ficus.{optionValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString("withDefault { }") -// arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") -// } -// -// def fallBackToApplyMethodDefaultValueNoKey = { -// import Ficus.{optionValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString("") -// arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") -// } -// -// def fallBackToConstructorDefaultValue = { -// import Ficus.{optionValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString("withDefault { }") -// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") -// } -// -// def fallBackToConstructorDefaultValueNoKey = { -// import Ficus.{optionValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString("") -// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") -// } -// -// def withOptionField = { -// import Ficus.{optionValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString("""withOption { option = "here" }""") -// arbitraryTypeValueReader[WithOption].value.read(cfg, "withOption").option must beEqualTo(Some("here")) -// } -// -// def ignoreApplyParamDefault = prop { (foo: String) => -// import Ficus.{optionValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") -// arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo(foo) -// } -// -// def ignoreConstructorParamDefault = prop { (foo: String) => -// import Ficus.{optionValueReader, stringValueReader} -// import ArbitraryTypeReader._ -// val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") -// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo(foo) -// } -// -// def overrideOptionReaderForDefault = { -// import ArbitraryTypeReader._ -// implicit val stringOptionReader: ValueReader[Option[String]] = Ficus.stringValueReader map { s => -// if (s.isEmpty) None else Some(s) -// } -// val cfg = ConfigFactory.parseString("""withDefault { foo = "" }""") -// arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") -// } -// -// def notChooseBetweenJavaConstructors = { -//// illTyped("implicitly[ValueReader[String]]") -//// illTyped("implicitly[ValueReader[Long]]") -//// illTyped("implicitly[ValueReader[Int]]") -//// illTyped("implicitly[ValueReader[Float]]") -//// illTyped("implicitly[ValueReader[Double]]") -//// illTyped("implicitly[ValueReader[Char]]") -// success // failure would result in compile error -// } -// -// def notTrumpCompanionReader = { -// import Ficus._ -// val cfg = ConfigFactory.parseString("""withReaderInCompanion { foo = "bar" }""") -// WithReaderInCompanion("from-companion") ==== cfg.as[WithReaderInCompanion]("withReaderInCompanion") -// } -// -// def useNameMapper = prop { (foo: String) => -// import Ficus.stringValueReader -// import ArbitraryTypeReader._ -// implicit val nameMapper: NameMapper = new NameMapper { -// override def map(name: String): String = name.toUpperCase -// } -// -// val cfg = ConfigFactory.parseString(s"singleParam { FOO = ${foo.asConfigValue} }") -// val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") -// instance.getFoo must beEqualTo(foo) -// } -//} -// -//object ArbitraryTypeReaderSpec { -// trait WithSimpleCompanionApply { -// def foo: String -// } -// -// object WithSimpleCompanionApply { -// def apply(foo2: String): WithSimpleCompanionApply = new WithSimpleCompanionApply { -// val foo = foo2 -// } -// } -// -// trait WithMultiCompanionApply { -// def foo: String -// def bar: Int -// } -// -// object WithMultiCompanionApply { -// def apply(foo: String, bar: Int): WithMultiCompanionApply = { -// val (_foo, _bar) = (foo, bar) -// new WithMultiCompanionApply { -// val foo = _foo -// val bar = _bar -// } -// } -// } -// -// trait WithDefault { -// def foo: String -// } -// -// object WithDefault { -// def apply(foo: String = "defaultFoo"): WithDefault = { -// val _foo = foo -// new WithDefault { -// val foo = _foo -// } -// } -// } -// -// trait WithOption { -// def option: Option[String] -// } -// -// object WithOption { -// def apply(option: Option[String]): WithOption = { -// val _option = option -// new WithOption { -// val option = _option -// } -// } -// } -// -// class ClassWithSingleParam(foo: String) { -// def getFoo = foo -// } -// -// class ClassWithMultipleParams(val foo: String, val bar: Int) -// -// class ClassWithDefault(val foo: String = "defaultFoo") -// -// trait WithMultipleApplyMethods { -// def foo: String -// } -// -// object WithMultipleApplyMethods { -// -// def apply(foo: Option[String]): Option[WithMultipleApplyMethods] = foo map { f => -// new WithMultipleApplyMethods { -// def foo: String = f -// } -// } -// -// def apply(foo: String): WithMultipleApplyMethods = { -// val _foo = foo -// new WithMultipleApplyMethods { -// def foo: String = _foo -// } -// } -// } -// -// class ClassWithMultipleConstructors(val foo: String) { -// def this(fooInt: Int) = this(fooInt.toString) -// } -// -// case class WithReaderInCompanion(foo: String) -// -// object WithReaderInCompanion { -// implicit val reader: ValueReader[WithReaderInCompanion] = -// ValueReader.relative(_ => WithReaderInCompanion("from-companion")) -// } -// -//} +package net.ceedubs.ficus +package readers + +import com.typesafe.config.ConfigFactory +import ConfigSerializerOps._ +//import shapeless.test.illTyped + +class ArbitraryTypeReaderSpec extends Spec { + def is = s2""" + An arbitrary type reader should + instantiate with a single-param apply method $instantiateSingleParamApply + instantiate with a single-param apply method from config itself $instantiateSingleParamApplyFromSelf + instantiate with no apply method but a single constructor with a single param $instantiateSingleParamConstructor + instantiate with a multi-param apply method $instantiateMultiParamApply + instantiate with no apply method but a single constructor with multiple params $instantiateMultiParamConstructor + instantiate with multiple apply methods if only one returns the correct type $multipleApply + instantiate with primary constructor when no apply methods and multiple constructors $multipleConstructors + use another implicit value reader for a field $withOptionField + fall back to a default value on an apply method $fallBackToApplyMethodDefaultValue + fall back to default values on an apply method if base key isn't in config $fallBackToApplyMethodDefaultValueNoKey + fall back to a default value on a constructor arg $fallBackToConstructorDefaultValue + fall back to a default values on a constructor if base key isn't in config $fallBackToConstructorDefaultValueNoKey + ignore a default value on an apply method if a value is in config $ignoreApplyParamDefault + ignore a default value in a constructor if a value is in config $ignoreConstructorParamDefault + allow overriding of option reader for default values $overrideOptionReaderForDefault + not choose between multiple Java constructors $notChooseBetweenJavaConstructors + not be prioritized over a Reader defined in a type's companion object (when Ficus._ is imported) $notTrumpCompanionReader + use name mapper $useNameMapper + """ + + import ArbitraryTypeReaderSpec._ + + def instantiateSingleParamApply = prop { (foo2: String) => + import Ficus.stringValueReader + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") + val instance: WithSimpleCompanionApply = + arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg, "simple") + instance.foo must beEqualTo(foo2) + } + + def instantiateSingleParamApplyFromSelf = prop { (foo2: String) => + import Ficus.stringValueReader + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"simple { foo2 = ${foo2.asConfigValue} }") + val instance: WithSimpleCompanionApply = + arbitraryTypeValueReader[WithSimpleCompanionApply].value.read(cfg.getConfig("simple"), ".") + instance.foo must beEqualTo(foo2) + } + + def instantiateSingleParamConstructor = prop { (foo: String) => + import Ficus.stringValueReader + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"singleParam { foo = ${foo.asConfigValue} }") + val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") + instance.getFoo must beEqualTo(foo) + } + + def instantiateMultiParamApply = prop { (foo: String, bar: Int) => + import Ficus.{intValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s""" + |multi { + | foo = ${foo.asConfigValue} + | bar = $bar + |}""".stripMargin) + val instance: WithMultiCompanionApply = arbitraryTypeValueReader[WithMultiCompanionApply].value.read(cfg, "multi") + (instance.foo must beEqualTo(foo)) and (instance.bar must beEqualTo(bar)) + } + + def instantiateMultiParamConstructor = prop { (foo: String, bar: Int) => + import Ficus.{intValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s""" + |multi { + | foo = ${foo.asConfigValue} + | bar = $bar + |}""".stripMargin) + val instance: ClassWithMultipleParams = arbitraryTypeValueReader[ClassWithMultipleParams].value.read(cfg, "multi") + (instance.foo must beEqualTo(foo)) and (instance.bar must beEqualTo(bar)) + } + + def multipleApply = prop { (foo: String) => + import Ficus.stringValueReader + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"withMultipleApply { foo = ${foo.asConfigValue} }") + val instance: WithMultipleApplyMethods = + arbitraryTypeValueReader[WithMultipleApplyMethods].value.read(cfg, "withMultipleApply") + instance.foo must beEqualTo(foo) + } + + def multipleConstructors = prop { (foo: String) => + import Ficus.stringValueReader + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"withMultipleConstructors { foo = ${foo.asConfigValue} }") + val instance: ClassWithMultipleConstructors = + arbitraryTypeValueReader[ClassWithMultipleConstructors].value.read(cfg, "withMultipleConstructors") + instance.foo must beEqualTo(foo) + } + + def fallBackToApplyMethodDefaultValue = { + import Ficus.{optionValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString("withDefault { }") + arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") + } + + def fallBackToApplyMethodDefaultValueNoKey = { + import Ficus.{optionValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString("") + arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") + } + + def fallBackToConstructorDefaultValue = { + import Ficus.{optionValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString("withDefault { }") + arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") + } + + def fallBackToConstructorDefaultValueNoKey = { + import Ficus.{optionValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString("") + arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") + } + + def withOptionField = { + import Ficus.{optionValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString("""withOption { option = "here" }""") + arbitraryTypeValueReader[WithOption].value.read(cfg, "withOption").option must beEqualTo(Some("here")) + } + + def ignoreApplyParamDefault = prop { (foo: String) => + import Ficus.{optionValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") + arbitraryTypeValueReader[WithDefault].value.read(cfg, "withDefault").foo must beEqualTo(foo) + } + + def ignoreConstructorParamDefault = prop { (foo: String) => + import Ficus.{optionValueReader, stringValueReader} + import ArbitraryTypeReader._ + val cfg = ConfigFactory.parseString(s"withDefault { foo = ${foo.asConfigValue} }") + arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo(foo) + } + + def overrideOptionReaderForDefault = { + import ArbitraryTypeReader._ + implicit val stringOptionReader: ValueReader[Option[String]] = Ficus.stringValueReader map { s => + if (s.isEmpty) None else Some(s) + } + val cfg = ConfigFactory.parseString("""withDefault { foo = "" }""") + arbitraryTypeValueReader[ClassWithDefault].value.read(cfg, "withDefault").foo must beEqualTo("defaultFoo") + } + + def notChooseBetweenJavaConstructors = { +// illTyped("implicitly[ValueReader[String]]") +// illTyped("implicitly[ValueReader[Long]]") +// illTyped("implicitly[ValueReader[Int]]") +// illTyped("implicitly[ValueReader[Float]]") +// illTyped("implicitly[ValueReader[Double]]") +// illTyped("implicitly[ValueReader[Char]]") + success // failure would result in compile error + } + + def notTrumpCompanionReader = { + import Ficus._ + val cfg = ConfigFactory.parseString("""withReaderInCompanion { foo = "bar" }""") + WithReaderInCompanion("from-companion") must beEqualTo(cfg.self.as[WithReaderInCompanion]("withReaderInCompanion")) + } + + def useNameMapper = prop { (foo: String) => + import Ficus.stringValueReader + import ArbitraryTypeReader._ + implicit val nameMapper: NameMapper = new NameMapper { + override def map(name: String): String = name.toUpperCase + } + + val cfg = ConfigFactory.parseString(s"singleParam { FOO = ${foo.asConfigValue} }") + val instance: ClassWithSingleParam = arbitraryTypeValueReader[ClassWithSingleParam].value.read(cfg, "singleParam") + instance.getFoo must beEqualTo(foo) + } +} + +object ArbitraryTypeReaderSpec { + trait WithSimpleCompanionApply { + def foo: String + } + + object WithSimpleCompanionApply { + def apply(foo2: String): WithSimpleCompanionApply = new WithSimpleCompanionApply { + val foo = foo2 + } + } + + trait WithMultiCompanionApply { + def foo: String + def bar: Int + } + + object WithMultiCompanionApply { + def apply(foo: String, bar: Int): WithMultiCompanionApply = { + val (_foo, _bar) = (foo, bar) + new WithMultiCompanionApply { + val foo = _foo + val bar = _bar + } + } + } + + trait WithDefault { + def foo: String + } + + object WithDefault { + def apply(foo: String = "defaultFoo"): WithDefault = { + val _foo = foo + new WithDefault { + val foo = _foo + } + } + } + + trait WithOption { + def option: Option[String] + } + + object WithOption { + def apply(option: Option[String]): WithOption = { + val _option = option + new WithOption { + val option = _option + } + } + } + + class ClassWithSingleParam(foo: String) { + def getFoo = foo + } + + class ClassWithMultipleParams(val foo: String, val bar: Int) + + class ClassWithDefault(val foo: String = "defaultFoo") + + trait WithMultipleApplyMethods { + def foo: String + } + + object WithMultipleApplyMethods { + + def apply(foo: Option[String]): Option[WithMultipleApplyMethods] = foo map { f => + new WithMultipleApplyMethods { + def foo: String = f + } + } + + def apply(foo: String): WithMultipleApplyMethods = { + val _foo = foo + new WithMultipleApplyMethods { + def foo: String = _foo + } + } + } + + class ClassWithMultipleConstructors(val foo: String) { + def this(fooInt: Int) = this(fooInt.toString) + } + + case class WithReaderInCompanion(foo: String) + + object WithReaderInCompanion { + implicit val reader: ValueReader[WithReaderInCompanion] = + ValueReader.relative(_ => WithReaderInCompanion("from-companion")) + } + +} diff --git a/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala index a9748ce..3dee7a1 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/CaseClassReadersSpec.scala @@ -1,156 +1,154 @@ -//package net.ceedubs.ficus -//package readers -// -//import com.typesafe.config.ConfigFactory -//import com.typesafe.config.Config -//import net.ceedubs.ficus.Ficus._ -//import net.ceedubs.ficus.readers.ArbitraryTypeReader._ -//import ConfigSerializerOps._ -// -//object CaseClassReadersSpec { -// case class SimpleCaseClass(bool: Boolean) -// case class MultipleFields(string: String, long: Long) -// case class WithOption(option: Option[String]) -// case class WithNestedCaseClass(simple: SimpleCaseClass) -// case class ValueClass(int: Int) extends AnyVal -// case class CompanionImplicit(value: Int) -// object CompanionImplicit { -// implicit val reader: ValueReader[CompanionImplicit] = -// implicitly[ValueReader[Int]].map(CompanionImplicit.apply) -// } -// case class WithNestedCompanionImplicit(value: CompanionImplicit) -// -// case class WithNestedValueClass(valueClass: ValueClass) -// case class WithDefault(string: String = "bar") -// case class Foo( -// bool: Boolean, -// intOpt: Option[Int], -// withNestedCaseClass: WithNestedCaseClass, -// withNestedValueClass: WithNestedValueClass -// ) -//} -// -//class CaseClassReadersSpec extends Spec { -// def is = s2""" -// A case class reader should -// be able to be used implicitly $useImplicitly -// hydrate a simple case class $hydrateSimpleCaseClass -// hydrate a simple case class from config itself $hydrateSimpleCaseClassFromSelf -// hydrate a case class with multiple fields $multipleFields -// use another implicit value reader for a field $withOptionField -// read a nested case class $withNestedCaseClass -// read a top-level value class $topLevelValueClass -// read a nested value class $nestedValueClass -// fall back to a default value $fallbackToDefault -// do a combination of these things $combination -// allow providing custom reader in companion $companionImplicitTopLevel -// allow providing custom reader in companion $nestedCompanionImplicit -// """ -// -// import CaseClassReadersSpec._ -// -// def useImplicitly = { -// val cfg = ConfigFactory.parseString("simple { bool = false }") -// cfg.as[SimpleCaseClass]("simple") must beEqualTo(SimpleCaseClass)(bool = false) -// } -// -// def hydrateSimpleCaseClass = prop { (bool: Boolean) => -// val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") -// cfg.as[SimpleCaseClass]("simple") must beEqualTo(SimpleCaseClass)(bool = bool) -// } -// -// def hydrateSimpleCaseClassFromSelf = prop { (bool: Boolean) => -// val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") -// cfg.getConfig("simple").as[SimpleCaseClass] must beEqualTo(SimpleCaseClass)(bool = bool) -// } -// -// def multipleFields = prop { (foo: String, long: Long) => -// val cfg = ConfigFactory.parseString(s""" -// |multipleFields { -// | string = ${foo.asConfigValue} -// | long = $long -// |} -// """.stripMargin) -// cfg.as[MultipleFields]("multipleFields") must beEqualTo(MultipleFields)(string = foo, long = long) -// } -// -// def withOptionField = prop { (s: String) => -// val cfg = ConfigFactory.parseString(s"""withOption { option = ${s.asConfigValue} }""") -// cfg.as[WithOption]("withOption") must beEqualTo(WithOption)(Some(s)) -// } -// -// def withNestedCaseClass = prop { (bool: Boolean) => -// val cfg = ConfigFactory.parseString(s""" -// |withNested { -// | simple { -// | bool = $bool -// | } -// |} -// """.stripMargin) -// cfg.as[WithNestedCaseClass]("withNested") must beEqualTo(WithNestedCaseClass)(simple = SimpleCaseClass(bool = bool)) -// } -// -// def topLevelValueClass = prop { (int: Int) => -// val cfg = ConfigFactory.parseString(s"valueClass { int = $int }") -// cfg.as[ValueClass]("valueClass") must beEqualTo(ValueClass)(int) -// } -// -// def nestedValueClass = prop { (int: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |withNestedValueClass { -// | valueClass = { -// | int = $int -// | } -// |} -// """.stripMargin) -// cfg.as[WithNestedValueClass]("withNestedValueClass") must beEqualTo(WithNestedValueClass)( -// valueClass = ValueClass(int = int) -// ) -// } -// -// def companionImplicitTopLevel = prop { (int: Int) => -// val cfg = ConfigFactory.parseString(s"value = $int ") -// cfg.as[CompanionImplicit]("value") must beEqualTo(CompanionImplicit)(int) -// } -// -// def nestedCompanionImplicit = prop { (int: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |withNestedCompanionImplicit { -// | value = $int -// |} -// """.stripMargin) -// cfg.as[WithNestedCompanionImplicit]("withNestedCompanionImplicit") must beEqualTo(WithNestedCompanionImplicit)( -// value = CompanionImplicit(value = int) -// ) -// } -// -// def fallbackToDefault = { -// val cfg = ConfigFactory.parseString("""withDefault { }""") -// cfg.as[WithDefault]("withDefault") must beEqualTo(WithDefault)() -// } -// -// def combination = prop { (fooBool: Boolean, simpleBool: Boolean, valueClassInt: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |foo { -// | bool = $fooBool -// | withNestedCaseClass { -// | simple { -// | bool = $simpleBool -// | } -// | } -// | withNestedValueClass = { -// | valueClass = { -// | int = $valueClassInt -// | } -// | } -// |} -// """.stripMargin) -// cfg.as[Foo]("foo") must beEqualTo(Foo)( -// bool = fooBool, -// intOpt = None, -// withNestedCaseClass = WithNestedCaseClass(simple = SimpleCaseClass(bool = simpleBool)), -// withNestedValueClass = WithNestedValueClass(ValueClass(int = valueClassInt)) -// ) -// } -// -//} +package net.ceedubs.ficus +package readers + +import com.typesafe.config.ConfigFactory +import com.typesafe.config.Config +import net.ceedubs.ficus.Ficus._ +import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +import ConfigSerializerOps._ + +object CaseClassReadersSpec { + case class SimpleCaseClass(bool: Boolean) + case class MultipleFields(string: String, long: Long) + case class WithOption(option: Option[String]) + case class WithNestedCaseClass(simple: SimpleCaseClass) + case class ValueClass(int: Int) extends AnyVal + case class CompanionImplicit(value: Int) + object CompanionImplicit { + implicit val reader: ValueReader[CompanionImplicit] = + implicitly[ValueReader[Int]].map(CompanionImplicit.apply) + } + case class WithNestedCompanionImplicit(value: CompanionImplicit) + + case class WithNestedValueClass(valueClass: ValueClass) + case class WithDefault(string: String = "bar") + case class Foo( + bool: Boolean, + intOpt: Option[Int], + withNestedCaseClass: WithNestedCaseClass, + withNestedValueClass: WithNestedValueClass + ) +} + +class CaseClassReadersSpec extends Spec { + def is = s2""" + A case class reader should + be able to be used implicitly $useImplicitly + hydrate a simple case class $hydrateSimpleCaseClass + hydrate a simple case class from config itself $hydrateSimpleCaseClassFromSelf + hydrate a case class with multiple fields $multipleFields + use another implicit value reader for a field $withOptionField + read a nested case class $withNestedCaseClass + read a top-level value class $topLevelValueClass + read a nested value class $nestedValueClass + fall back to a default value $fallbackToDefault + do a combination of these things $combination + allow providing custom reader in companion $companionImplicitTopLevel + allow providing custom reader in companion $nestedCompanionImplicit + """ + + import CaseClassReadersSpec._ + + def useImplicitly = { + val cfg: FicusConfig = ConfigFactory.parseString("simple { bool = false }") + cfg.as[SimpleCaseClass]("simple") must beEqualTo(SimpleCaseClass(bool = false)) + } + + def hydrateSimpleCaseClass = prop { (bool: Boolean) => + val cfg: FicusConfig = ConfigFactory.parseString(s"simple { bool = $bool }") + cfg.as[SimpleCaseClass]("simple") must beEqualTo(SimpleCaseClass(bool = bool)) + } + + def hydrateSimpleCaseClassFromSelf = prop { (bool: Boolean) => + val cfg = ConfigFactory.parseString(s"simple { bool = $bool }") + cfg.getConfig("simple").to[SimpleCaseClass] must beEqualTo(SimpleCaseClass(bool = bool)) + } + + def multipleFields = prop { (foo: String, long: Long) => + val cfg: FicusConfig = ConfigFactory.parseString(s""" + |multipleFields { + | string = ${foo.asConfigValue} + | long = $long + |} + """.stripMargin) + cfg.as[MultipleFields]("multipleFields") must beEqualTo(MultipleFields(string = foo, long = long)) + } + + def withOptionField = prop { (s: String) => + val cfg: FicusConfig = ConfigFactory.parseString(s"""withOption { option = ${s.asConfigValue} }""") + cfg.as[WithOption]("withOption") must beEqualTo(WithOption(Some(s))) + } + + def withNestedCaseClass = prop { (bool: Boolean) => + val cfg: FicusConfig = ConfigFactory.parseString(s""" + |withNested { + | simple { + | bool = $bool + | } + |} + """.stripMargin) + cfg.as[WithNestedCaseClass]("withNested") must beEqualTo(WithNestedCaseClass(simple = SimpleCaseClass(bool = bool))) + } + + def topLevelValueClass = prop { (int: Int) => + val cfg: FicusConfig = ConfigFactory.parseString(s"valueClass { int = $int }") + cfg.as[ValueClass]("valueClass") must beEqualTo(ValueClass(int)) + } + + def nestedValueClass = prop { (int: Int) => + val cfg: FicusConfig = ConfigFactory.parseString(s""" + |withNestedValueClass { + | valueClass = { + | int = $int + | } + |} + """.stripMargin) + cfg.as[WithNestedValueClass]("withNestedValueClass") must beEqualTo( + WithNestedValueClass(valueClass = ValueClass(int = int))) + } + + def companionImplicitTopLevel = prop { (int: Int) => + val cfg: FicusConfig = ConfigFactory.parseString(s"value = $int ") + cfg.as[CompanionImplicit]("value") must beEqualTo(CompanionImplicit(int)) + } + + def nestedCompanionImplicit = prop { (int: Int) => + val cfg: FicusConfig = ConfigFactory.parseString(s""" + |withNestedCompanionImplicit { + | value = $int + |} + """.stripMargin) + cfg.as[WithNestedCompanionImplicit]("withNestedCompanionImplicit") must beEqualTo( + WithNestedCompanionImplicit(value = CompanionImplicit(value = int))) + } + + def fallbackToDefault = { + val cfg: FicusConfig = ConfigFactory.parseString("""withDefault { }""") + cfg.as[WithDefault]("withDefault") must beEqualTo(WithDefault()) + } + + def combination = prop { (fooBool: Boolean, simpleBool: Boolean, valueClassInt: Int) => + val cfg: FicusConfig = ConfigFactory.parseString(s""" + |foo { + | bool = $fooBool + | withNestedCaseClass { + | simple { + | bool = $simpleBool + | } + | } + | withNestedValueClass = { + | valueClass = { + | int = $valueClassInt + | } + | } + |} + """.stripMargin) + cfg.as[Foo]("foo") must beEqualTo(Foo( + bool = fooBool, + intOpt = None, + withNestedCaseClass = WithNestedCaseClass(simple = SimpleCaseClass(bool = simpleBool)), + withNestedValueClass = WithNestedValueClass(ValueClass(int = valueClassInt)) + )) + } + +} diff --git a/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala index 1f15fe5..59e9216 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ConfigReaderSpec.scala @@ -1,61 +1,61 @@ -//package net.ceedubs.ficus -//package readers -// -//import com.typesafe.config.{Config, ConfigFactory} -//import Ficus._ -// -//class ConfigReaderSpec extends Spec { -// def is = s2""" -// The Config value reader should -// read a config $readConfig -// implicitly read a config $implicitlyReadConfig -// read a ficus config $readFicusConfig -// implicitly read a ficus config $implicitlyReadFicusConfig -// implicitly read a ficus config itself $implicitlyReadFicusConfigFromSelf -// """ -// -// def readConfig = prop { (i: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |myConfig { -// | myValue = $i -// |} -// """.stripMargin) -// configValueReader.read(cfg, "myConfig").getInt("myValue") must beEqualTo(i) -// } -// -// def implicitlyReadConfig = prop { (i: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |myConfig { -// | myValue = $i -// |} -// """.stripMargin) -// cfg.as[Config]("myConfig").getInt("myValue") must beEqualTo(i) -// } -// -// def readFicusConfig = prop { (i: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |myConfig { -// | myValue = $i -// |} -// """.stripMargin) -// ficusConfigValueReader.read(cfg, "myConfig").as[Int]("myValue") must beEqualTo(i) -// } -// -// def implicitlyReadFicusConfig = prop { (i: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |myConfig { -// | myValue = $i -// |} -// """.stripMargin) -// cfg.as[FicusConfig]("myConfig").as[Int]("myValue") must beEqualTo(i) -// } -// -// def implicitlyReadFicusConfigFromSelf = prop { (i: Int) => -// val cfg = ConfigFactory.parseString(s""" -// |myConfig { -// | myValue = $i -// |} -// """.stripMargin) -// cfg.getConfig("myConfig").as[FicusConfig].as[Int]("myValue") must beEqualTo(i) -// } -//} +package net.ceedubs.ficus +package readers + +import com.typesafe.config.{Config, ConfigFactory} +import Ficus._ + +class ConfigReaderSpec extends Spec { + def is = s2""" + The Config value reader should + read a config $readConfig + implicitly read a config $implicitlyReadConfig + read a ficus config $readFicusConfig + implicitly read a ficus config $implicitlyReadFicusConfig + implicitly read a ficus config itself $implicitlyReadFicusConfigFromSelf + """ + + def readConfig = prop { (i: Int) => + val cfg = ConfigFactory.parseString(s""" + |myConfig { + | myValue = $i + |} + """.stripMargin) + configValueReader.read(cfg, "myConfig").getInt("myValue") must beEqualTo(i) + } + + def implicitlyReadConfig = prop { (i: Int) => + val cfg: FicusConfig = ConfigFactory.parseString(s""" + |myConfig { + | myValue = $i + |} + """.stripMargin) + cfg.as[Config]("myConfig").getInt("myValue") must beEqualTo(i) + } + + def readFicusConfig = prop { (i: Int) => + val cfg = ConfigFactory.parseString(s""" + |myConfig { + | myValue = $i + |} + """.stripMargin) + ficusConfigValueReader.read(cfg, "myConfig").as[Int]("myValue") must beEqualTo(i) + } + + def implicitlyReadFicusConfig = prop { (i: Int) => + val cfg: FicusConfig = ConfigFactory.parseString(s""" + |myConfig { + | myValue = $i + |} + """.stripMargin) + cfg.as[FicusConfig]("myConfig").as[Int]("myValue") must beEqualTo(i) + } + + def implicitlyReadFicusConfigFromSelf = prop { (i: Int) => + val cfg = ConfigFactory.parseString(s""" + |myConfig { + | myValue = $i + |} + """.stripMargin) + cfg.getConfig("myConfig").self.as[Int]("myValue") must beEqualTo(i) + } +} diff --git a/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala index 1cf7ecf..f507517 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/DurationReadersSpec.scala @@ -1,55 +1,56 @@ -//package net.ceedubs.ficus -//package readers -// -//import com.typesafe.config.ConfigFactory -// -//import scala.concurrent.duration._ -//import org.scalacheck.{Gen, Prop} -// -//class DurationReadersSpec extends Spec with DurationReaders { -// def is = s2""" -// The finite duration reader should -// read a millisecond value ${readMillis(finiteDurationReader)} -// read a minute value ${readMinutes(finiteDurationReader)} -// read a days value into days ${readDaysUnit(finiteDurationReader)} -// -// The duration reader should -// read a millisecond value ${readMillis(durationReader)} -// read a minute value ${readMinutes(durationReader)} -// read a days value into days ${readDaysUnit(durationReader)} -// read positive infinite values $readPositiveInf -// read negative infinite values $readNegativeInf -// """ -// -// def readMillis[T](reader: ValueReader[T]) = prop { (i: Int) => -// val cfg = ConfigFactory.parseString(s"myValue = $i") -// reader.read(cfg, "myValue") must beEqualTo(i millis) -// } -// -// def readMinutes[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-1.5e8.toInt, 1.5e8.toInt)) { (i: Int) => -// val cfg = ConfigFactory.parseString("myValue = \"" + i + " minutes\"") -// reader.read(cfg, "myValue") must beEqualTo(i minutes) -// } -// -// def readDaysUnit[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-106580, 106580)) { (i: Int) => -// val str = i.toString + " day" + (if (i == 1) "" else "s") -// val cfg = ConfigFactory.parseString(s"""myValue = "$str" """) -// reader.read(cfg, "myValue").toString must beEqualTo(str) -// } -// -// def readPositiveInf = { -// val positiveInf = List("Inf", "PlusInf", "\"+Inf\"") -// positiveInf.forall { (s: String) => -// val cfg = ConfigFactory.parseString(s"myValue = $s") -// durationReader.read(cfg, "myValue") should be(Duration.Inf) -// } -// } -// -// def readNegativeInf = { -// val negativeInf = List("-Inf", "MinusInf") -// negativeInf.forall { (s: String) => -// val cfg = ConfigFactory.parseString(s"myValue = $s") -// durationReader.read(cfg, "myValue") should be(Duration.MinusInf) -// } -// } -//} +package net.ceedubs.ficus +package readers + +import com.typesafe.config.ConfigFactory + +import scala.concurrent.duration._ +import scala.language.postfixOps +import org.scalacheck.{Gen, Prop} + +class DurationReadersSpec extends Spec with DurationReaders { + def is = s2""" + The finite duration reader should + read a millisecond value ${readMillis(finiteDurationReader)} + read a minute value ${readMinutes(finiteDurationReader)} + read a days value into days ${readDaysUnit(finiteDurationReader)} + + The duration reader should + read a millisecond value ${readMillis(durationReader)} + read a minute value ${readMinutes(durationReader)} + read a days value into days ${readDaysUnit(durationReader)} + read positive infinite values $readPositiveInf + read negative infinite values $readNegativeInf + """ + + def readMillis[T](reader: ValueReader[T]) = prop { (i: Int) => + val cfg = ConfigFactory.parseString(s"myValue = $i") + reader.read(cfg, "myValue") must beEqualTo(i millis) + } + + def readMinutes[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-1.5e8.toInt, 1.5e8.toInt)) { (i: Int) => + val cfg = ConfigFactory.parseString("myValue = \"" + i + " minutes\"") + reader.read(cfg, "myValue") must beEqualTo(i minutes) + } + + def readDaysUnit[T](reader: ValueReader[T]) = Prop.forAll(Gen.choose(-106580, 106580)) { (i: Int) => + val str = i.toString + " day" + (if (i == 1) "" else "s") + val cfg = ConfigFactory.parseString(s"""myValue = "$str" """) + reader.read(cfg, "myValue").toString must beEqualTo(str) + } + + def readPositiveInf = { + val positiveInf = List("Inf", "PlusInf", "\"+Inf\"") + forall(positiveInf) { (s: String) => + val cfg = ConfigFactory.parseString(s"myValue = $s") + durationReader.read(cfg, "myValue") should be(Duration.Inf) + } + } + + def readNegativeInf = { + val negativeInf = List("-Inf", "MinusInf") + forall(negativeInf) { (s: String) => + val cfg = ConfigFactory.parseString(s"myValue = $s") + durationReader.read(cfg, "myValue") should be(Duration.MinusInf) + } + } +} diff --git a/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala index de0308e..f5ce9e5 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/ISOZonedDateTimeReaderSpec.scala @@ -3,8 +3,7 @@ package readers import java.time.{ZoneId, ZonedDateTime} -import com.typesafe.config.ConfigFactory - +import com.typesafe.config.{Config, ConfigFactory} import Ficus._ class ISOZonedDateTimeReaderSpec extends Spec { @@ -14,12 +13,12 @@ class ISOZonedDateTimeReaderSpec extends Spec { """ def readZonedDateTime = { - val cfg = ConfigFactory.parseString(s""" + val cfg: FicusConfig = ConfigFactory.parseString(s""" | foo { | date = "2016-02-28T11:46:26.896+01:00[Europe/Berlin]" | } """.stripMargin) - val date = cfg.to[ZonedDateTime]("foo.date") + val date = cfg.as[ZonedDateTime]("foo.date") val expected = ZonedDateTime.of( 2016, 2, diff --git a/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala index 8ea7a8d..1fbd130 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/LocalDateReaderSpec.scala @@ -12,13 +12,13 @@ class LocalDateReaderSpec extends Spec { """ def readLocalDate = { - val cfg = ConfigFactory.parseString(s""" + val cfg: FicusConfig = ConfigFactory.parseString(s""" | foo { | date = "2003-01-03" | } """.stripMargin) - val localDate = cfg.to[LocalDate]("foo.date") - val expected = LocalDate.of(2003, 1, 3) + val localDate = cfg.as[LocalDate]("foo.date") + val expected = LocalDate.of(2003, 1, 3) localDate should beEqualTo(expected) } } diff --git a/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala index 32bf4b4..e056b3d 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/PeriodReaderSpec.scala @@ -2,36 +2,36 @@ package net.ceedubs.ficus package readers import java.time.Period -import com.typesafe.config.{ Config, ConfigFactory } +import com.typesafe.config.{Config, ConfigFactory} import Ficus._ class PeriodReaderSpec extends Spec { val periodReaderExists = implicitly[ValueReader[Period]] - def is = s2""" + def is = s2""" The PeriodReader should read a Period in ISO-8601 format $readPeriod read a negative Period $readNegativePeriod """ def readPeriod = { - val cfg: Config = ConfigFactory.parseString(s""" + val cfg: FicusConfig = ConfigFactory.parseString(s""" | foo { | interval = "P1Y3M10D" | } """.stripMargin) - val period: Period = cfg.to("foo.interval") - val expected = Period.of(1, 3, 10) + val period: Period = cfg.as[Period]("foo.interval") + val expected = Period.of(1, 3, 10) period should beEqualTo(expected) } def readNegativePeriod = { - val cfg = ConfigFactory.parseString(s""" + val cfg: FicusConfig = ConfigFactory.parseString(s""" | foo { | interval = "P-1Y10M3D" | } """.stripMargin) - val period = cfg.to[Period]("foo.interval") - val expected = Period.of(-1, 10, 3) + val period = cfg.as[Period]("foo.interval") + val expected = Period.of(-1, 10, 3) period should beEqualTo(expected) } } diff --git a/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala b/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala index 51dc503..3998bdc 100644 --- a/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala +++ b/src/test/scala/net/ceedubs/ficus/readers/URLReaderSpec.scala @@ -1,28 +1,28 @@ -//package net.ceedubs.ficus.readers -// -//import java.net.URL -// -//import com.typesafe.config.ConfigException.WrongType -//import com.typesafe.config.ConfigFactory -//import net.ceedubs.ficus.Spec -//import org.specs2.matcher.MatchResult -// -//class URLReaderSpec extends Spec with URLReader with TryReader { -// def is = s2""" -// The URL value reader should -// read a valid URL $readValidURL -// detect wrong type on malformed URL (with an unsupported protocol) $readMalformedURL -// """ -// -// def readValidURL: MatchResult[URL] = { -// val url = """https://www.google.com""" -// val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + url + "\""}") -// javaURLReader.read(cfg, "myValue") must beEqualTo(new URL(url)) -// } -// -// def readMalformedURL: MatchResult[Any] = { -// val malformedUrl = """foo://bar.com""" -// val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + malformedUrl + "\""}") -// javaURLReader.read(cfg, "myValue") must throwA[WrongType] -// } -//} +package net.ceedubs.ficus.readers + +import java.net.URL + +import com.typesafe.config.ConfigException.WrongType +import com.typesafe.config.ConfigFactory +import net.ceedubs.ficus.Spec +import org.specs2.execute.Result + +class URLReaderSpec extends Spec with URLReader with TryReader { + def is = s2""" + The URL value reader should + read a valid URL $readValidURL + detect wrong type on malformed URL (with an unsupported protocol) $readMalformedURL + """ + + def readValidURL: Result = { + val url = """https://www.google.com""" + val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + url + "\""}") + javaURLReader.read(cfg, "myValue") must beEqualTo(new URL(url)) + } + + def readMalformedURL: Result = { + val malformedUrl = """foo://bar.com""" + val cfg = ConfigFactory.parseString(s"myValue = ${"\"" + malformedUrl + "\""}") + javaURLReader.read(cfg, "myValue") must throwA[WrongType] + } +}