Skip to content

Commit

Permalink
Merge 115172a into 8fea3ba
Browse files Browse the repository at this point in the history
  • Loading branch information
yangzai committed Jan 13, 2019
2 parents 8fea3ba + 115172a commit 14c422f
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 8 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS
Expand Up @@ -19,3 +19,4 @@ Yuriy Badalyantc <lmnet89@gmail.com> @lmnet
Anshul Bajpai @anshulbajpai
Nicolae Namolovan @NicolaeNMV
Dmitry Polienko @nigredo-tori
Hao Yang ANG <yan9za1@gmail.com> @yangzai
12 changes: 7 additions & 5 deletions modules/cats/README.md
Expand Up @@ -17,9 +17,9 @@ libraryDependencies += "com.github.pureconfig" %% "pureconfig-cats" % "0.10.1"
### Reading cats data structures from a config

The following cats data structures are supported:

* `NonEmptyList`, `NonEmptyVector`, `NonEmptySet`
* `NonEmptyList`, `NonEmptyVector`, `NonEmptySet`, `NonEmptyChain`
* `NonEmptyMap[K, V]` implicits of `ConfigReader[Map[K, V]]` and `Order[K]` should be in the scope.
* Cats data types with `Foldable` and `Alternative` (i.e. non-reducible) typeclass instances, e.g. `Chain`.
For example, if your key is a `String` then `Order[String]` can be imported from `cats.instances.string._`

All of these data structures rely on the instances of their unrestricted (i.e. possibly empty) variants.
Expand All @@ -28,7 +28,7 @@ Custom collection readers, if any, may affect the behavior of these too.
Here is an example of usage:

```scala
import cats.data.{NonEmptyList, NonEmptySet, NonEmptyVector, NonEmptyMap}
import cats.data.{NonEmptyList, NonEmptySet, NonEmptyVector, NonEmptyMap, NonEmptyChain}
import cats.instances.string._
import com.typesafe.config.ConfigFactory.parseString
import pureconfig._
Expand All @@ -39,7 +39,8 @@ case class MyConfig(
numberList: NonEmptyList[Int],
numberSet: NonEmptySet[Int],
numberVector: NonEmptyVector[Int],
numberMap: NonEmptyMap[String, Int]
numberMap: NonEmptyMap[String, Int],
numberChain: NonEmptyChain[Int]
)
```

Expand All @@ -49,7 +50,8 @@ val conf = parseString("""{
number-list: [1,2,3],
number-set: [1,2,3],
number-vector: [1,2,3],
number-map { "one": 1, "two": 2, "three": 3 }
number-map { "one": 1, "two": 2, "three": 3 },
number-chain: [1,2,3]
}""")
// conf: com.typesafe.config.Config = Config(SimpleConfigObject({"number-list":[1,2,3],"number-map":{"one":1,"three":3,"two":2},"number-set":[1,2,3],"number-vector":[1,2,3]}))

Expand Down
3 changes: 2 additions & 1 deletion modules/cats/build.sbt
Expand Up @@ -2,7 +2,8 @@ name := "pureconfig-cats"

libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "1.4.0",
"org.typelevel" %% "cats-laws" % "1.4.0" % "test")
"org.typelevel" %% "cats-laws" % "1.4.0" % "test",
Dependencies.shapeless % Provided)

developers := List(
Developer("derekmorr", "Derek Morr", "morr.derek@gmail.com", url("https://github.com/derekmorr")),
Expand Down
21 changes: 20 additions & 1 deletion modules/cats/src/main/scala/pureconfig/module/cats/package.scala
@@ -1,10 +1,14 @@
package pureconfig.module

import _root_.cats.data.{ NonEmptyList, NonEmptyMap, NonEmptySet, NonEmptyVector }
import _root_.cats.data._
import _root_.cats.kernel.Order
import _root_.cats.{ Alternative, Foldable }
import _root_.cats.implicits._
import pureconfig.{ ConfigReader, ConfigWriter }
import shapeless._

import scala.collection.immutable.{ SortedMap, SortedSet }
import scala.language.higherKinds
import scala.reflect.ClassTag

/**
Expand Down Expand Up @@ -34,4 +38,19 @@ package object cats {
fromNonEmpty(reader)(x => NonEmptyMap.fromMap(SortedMap(x.toSeq: _*)(ord.toOrdering)))
implicit def nonEmptyMapWriter[A, B](implicit writer: ConfigWriter[Map[A, B]]): ConfigWriter[NonEmptyMap[A, B]] =
writer.contramap(_.toSortedMap)

// For emptiable foldables not covered by TraversableOnce reader/writer, e.g. Chain.
implicit def nonReducibleReader[T, F[_]: Foldable: Alternative](implicit
reader: ConfigReader[TraversableOnce[T]],
ev: ¬¬[F[T]] <:!< (TraversableOnce[T] ∨ Option[T])): ConfigReader[F[T]] =
reader.map(to => (to :\ Alternative[F].empty[T])(_.pure[F] <+> _))
implicit def nonReducibleWriter[T, F[_]: Foldable: Alternative](implicit
writer: ConfigWriter[TraversableOnce[T]],
ev: ¬¬[F[T]] <:!< (TraversableOnce[T] ∨ Option[T])): ConfigWriter[F[T]] =
writer.contramap(_.toList)

implicit def nonEmptyChainReader[T](implicit reader: ConfigReader[Chain[T]]): ConfigReader[NonEmptyChain[T]] =
fromNonEmpty(reader)(NonEmptyChain.fromChain)
implicit def nonEmptyChainWriter[T](implicit writer: ConfigWriter[Chain[T]]): ConfigWriter[NonEmptyChain[T]] =
writer.contramap(_.toChain)
}
@@ -1,6 +1,6 @@
package pureconfig.module.cats

import cats.data.{ NonEmptyList, NonEmptyMap, NonEmptySet, NonEmptyVector }
import cats.data._
import cats.instances.int._
import cats.instances.string._
import com.typesafe.config.ConfigFactory.parseString
Expand All @@ -16,13 +16,17 @@ class CatsSuite extends BaseSuite with ConfigConvertChecks {
case class NumVec(numbers: NonEmptyVector[Int])
case class NumSet(numbers: NonEmptySet[Int])
case class NumMap(numbers: NonEmptyMap[String, Int])
case class NumChain(numbers: NonEmptyChain[Int])

checkReadWrite[Numbers](parseString(s"""{ numbers: [1,2,3] }""").root() Numbers(NonEmptyList(1, List(2, 3))))
checkReadWrite[NumVec](parseString(s"""{ numbers: [1,2,3] }""").root() NumVec(NonEmptyVector(1, Vector(2, 3))))
checkReadWrite[NumSet](parseString(s"""{ numbers: [1,2,3] }""").root() NumSet(NonEmptySet(1, SortedSet(2, 3))))
checkReadWrite[NumMap](parseString(s"""{
numbers {"1": 1, "2": 2, "3": 3 }
}""").root() NumMap(NonEmptyMap(("1", 1), SortedMap("2" 2, "3" 3))))
// Chain seems to break referential transparency unless prepend/append order is the same
checkReadWrite[NumChain](parseString(s"""{ numbers: [1,2,3] }""").root()
NumChain(NonEmptyChain.fromChainPrepend(1, 2 +: 3 +: Chain.empty)))

it should "return an EmptyTraversableFound when reading empty lists into NonEmptyList" in {
val config = parseString("{ numbers: [] }")
Expand All @@ -43,4 +47,9 @@ class CatsSuite extends BaseSuite with ConfigConvertChecks {
val config = parseString("{ numbers{} }")
config.to[NumMap] should failWith(EmptyTraversableFound("scala.collection.immutable.Map"), "numbers")
}

it should "return an EmptyTraversableFound when reading empty map into NonEmptyChain" in {
val config = parseString("{ numbers: [] }")
config.to[NumChain] should failWith(EmptyTraversableFound("cats.data.Chain"), "numbers")
}
}

0 comments on commit 14c422f

Please sign in to comment.