Skip to content

Commit

Permalink
Fixes after review
Browse files Browse the repository at this point in the history
  • Loading branch information
a-khakimov committed Feb 14, 2023
1 parent 241609d commit 547f9f7
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 74 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ val hocon = hoconAt(config)("rate")
hocon("elements").as[Int],
hocon("burst-duration").as[FiniteDuration],
hocon("check-interval").as[Period],
hocon.list("values").as[List[String]]
hocon("values").as[List[String]]
).parMapN(Rate.apply).load[IO].map { rate =>
assertEquals(rate.burstDuration, 100.millis)
assertEquals(rate.checkInterval, Period.ofWeeks(2))
Expand Down
79 changes: 16 additions & 63 deletions src/main/scala/Hocon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@

package lt.dvim.ciris

import scala.concurrent.duration.FiniteDuration
import scala.jdk.CollectionConverters._
import scala.util.Try

import cats.Show
import ciris._
import com.typesafe.config.{Config, ConfigException, ConfigFactory, ConfigValue => HoconConfigValue}

Expand All @@ -28,21 +27,13 @@ object Hocon extends HoconConfigDecoders {
final class HoconAt(config: Config, path: String) {
def apply(name: String): ConfigValue[Effect, HoconConfigValue] =
Try(config.getValue(fullPath(name))).fold(
errHandler(name),
{
case _: ConfigException.Missing => ConfigValue.missing(key(name))
case ex => ConfigValue.failed(ConfigError(ex.getMessage))
},
ConfigValue.loaded(key(name), _)
)

def list(name: String): ConfigValue[Effect, HoconConfigValue] =
Try(config.getList(fullPath(name))).fold(
errHandler(name),
ConfigValue.loaded(key(name), _)
)

private def errHandler(name: String): Throwable => ConfigValue[Effect, HoconConfigValue] = {
case _: ConfigException.Missing => ConfigValue.missing(key(name))
case ex => ConfigValue.failed(ConfigError(ex.getMessage))
}

private def key(name: String) = ConfigKey(fullPath(name))
private def fullPath(name: String) = s"$path.$name"
}
Expand All @@ -58,48 +49,17 @@ trait HoconConfigDecoders {
implicit val stringHoconDecoder: ConfigDecoder[HoconConfigValue, String] =
ConfigDecoder[HoconConfigValue].map(_.atKey("t").getString("t"))

private implicit val show: Show[HoconConfigValue] = new Show[HoconConfigValue]() {
def show(t: HoconConfigValue): String = t.toString
}

implicit val listStringHoconDecoder: ConfigDecoder[HoconConfigValue, List[String]] =
ConfigDecoder[HoconConfigValue].mapOption("List[String]") { c =>
Try(asScalaList(c.atKey("t").getStringList("t"))).toOption
}

implicit val listIntHoconDecoder: ConfigDecoder[HoconConfigValue, List[Int]] =
ConfigDecoder[HoconConfigValue].mapOption("List[Int]") { c =>
Try(asScalaList(c.atKey("t").getIntList("t")).map(_.intValue())).toOption
}

implicit val listLongHoconDecoder: ConfigDecoder[HoconConfigValue, List[Long]] =
ConfigDecoder[HoconConfigValue].mapOption("List[Long]") { c =>
Try(asScalaList(c.atKey("t").getLongList("t")).map(_.longValue())).toOption
}

implicit val listBooleanHoconDecoder: ConfigDecoder[HoconConfigValue, List[Boolean]] =
ConfigDecoder[HoconConfigValue].mapOption("List[Boolean]") { c =>
Try(asScalaList(c.atKey("t").getBooleanList("t")).map(_.booleanValue())).toOption
}

implicit val listDoubleHoconDecoder: ConfigDecoder[HoconConfigValue, List[Double]] =
ConfigDecoder[HoconConfigValue].mapOption("List[Double]") { c =>
Try(asScalaList(c.atKey("t").getDoubleList("t")).map(_.doubleValue())).toOption
}

implicit val listJavaDurationHoconDecoder: ConfigDecoder[HoconConfigValue, List[java.time.Duration]] =
ConfigDecoder[HoconConfigValue].mapOption("List[java.time.Duration]") { c =>
Try(asScalaList(c.atKey("t").getDurationList("t"))).toOption
}

implicit val listDurationHoconDecoder: ConfigDecoder[HoconConfigValue, List[FiniteDuration]] =
ConfigDecoder[HoconConfigValue].mapOption("List[FiniteDuration]") { c =>
Try {
asScalaList(c.atKey("t").getDurationList("t"))
.map(_.toNanos)
.map(scala.concurrent.duration.Duration.fromNanos)
}.toOption
}
implicit def listHoconDecoder[T](implicit
decoder: ConfigDecoder[HoconConfigValue, T]
): ConfigDecoder[HoconConfigValue, List[T]] =
ConfigDecoder[HoconConfigValue]
.map(_.atKey("t").getList("t").asScala.toList)
.mapEither { (key, list) =>
list.map(decoder.decode(key, _)).partitionMap(identity) match {
case (Nil, rights) => Right(rights)
case (firstLeft :: _, _) => Left(firstLeft)
}
}

implicit val javaTimeDurationHoconDecoder: ConfigDecoder[HoconConfigValue, java.time.Duration] =
ConfigDecoder[HoconConfigValue].map(_.atKey("t").getDuration("t"))
Expand All @@ -109,11 +69,4 @@ trait HoconConfigDecoders {

implicit def throughStringHoconDecoder[T](implicit d: ConfigDecoder[String, T]): ConfigDecoder[HoconConfigValue, T] =
stringHoconDecoder.as[T]

private def asScalaList[T](collection: java.util.Collection[T]): List[T] = {
val builder = List.newBuilder[T]
val it = collection.iterator()
while (it.hasNext) builder += it.next()
builder.result()
}
}
19 changes: 9 additions & 10 deletions src/test/scala/HoconSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,41 +66,40 @@ class HoconSpec extends CatsEffectSuite {
nested("per").as[java.time.Period].load[IO] assertEquals java.time.Period.ofWeeks(2)
}
test("parse List[Int]") {
nested.list("listInt").as[List[Int]].load[IO] assertEquals List(1, 2, 3, 4)
nested("listInt").as[List[Int]].load[IO] assertEquals List(1, 2, 3, 4)
}
test("parse List[Long]") {
nested.list("listInt").as[List[Long]].load[IO] assertEquals List(1L, 2, 3, 4)
nested("listInt").as[List[Long]].load[IO] assertEquals List(1L, 2, 3, 4)
}
test("parse List[String]") {
nested.list("listString").as[List[String]].load[IO] assertEquals List("a", "b", "c", "d")
nested("listString").as[List[String]].load[IO] assertEquals List("a", "b", "c", "d")
}
test("parse List[Bool]") {
nested.list("listBool").as[List[Boolean]].load[IO] assertEquals List(true, false, true)
nested("listBool").as[List[Boolean]].load[IO] assertEquals List(true, false, true)
}
test("parse List[Double]") {
nested.list("listDouble").as[List[Double]].load[IO] assertEquals List(1.12, 2.34, 2.33)
nested("listDouble").as[List[Double]].load[IO] assertEquals List(1.12, 2.34, 2.33)
}
test("parse List[java Duration]") {
nested.list("listDur").as[List[java.time.Duration]].load[IO] assertEquals List(
nested("listDur").as[List[java.time.Duration]].load[IO] assertEquals List(
java.time.Duration.ofMillis(10),
java.time.Duration.ofMillis(15),
java.time.Duration.ofSeconds(1)
)
}
test("parse List[scala Duration]") {
nested.list("listDur").as[List[FiniteDuration]].load[IO] assertEquals List(10.millis, 15.millis, 1.second)
nested("listDur").as[List[FiniteDuration]].load[IO] assertEquals List(10.millis, 15.millis, 1.second)
}
test("handle decode error for invalid list") {
nested
.list("invalidList")
nested("invalidList")
.as[List[Int]]
.attempt[IO]
.map {
case Left(error) => error.messages.toList.head
case Right(_) => "config loaded"
}
.assertEquals(
"Nested.config.invalidList with value SimpleConfigList([1,\"a\",true]) cannot be converted to List[Int]"
"Nested.config.invalidList with value a cannot be converted to Int"
)
}
test("handle missing") {
Expand Down

0 comments on commit 547f9f7

Please sign in to comment.