Skip to content

Commit

Permalink
Merge e3d0892 into 140231f
Browse files Browse the repository at this point in the history
  • Loading branch information
yangzai committed Jan 22, 2019
2 parents 140231f + e3d0892 commit 39d721f
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 15 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
15 changes: 9 additions & 6 deletions modules/cats/README.md
Expand Up @@ -18,8 +18,9 @@ libraryDependencies += "com.github.pureconfig" %% "pureconfig-cats" % "0.10.1"

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 +29,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 +40,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,12 +51,13 @@ 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]}))
// conf: com.typesafe.config.Config = Config(SimpleConfigObject({"number-chain":[1,2,3],"number-list":[1,2,3],"number-map":{"one":1,"three":3,"two":2},"number-set":[1,2,3],"number-vector":[1,2,3]}))

loadConfig[MyConfig](conf)
// res0: pureconfig.ConfigReader.Result[MyConfig] = Right(MyConfig(NonEmptyList(1, 2, 3),TreeSet(1, 2, 3),NonEmptyVector(1, 2, 3),Map(one -> 1, three -> 3, two -> 2)))
// res0: pureconfig.ConfigReader.Result[MyConfig] = Right(MyConfig(NonEmptyList(1, 2, 3),TreeSet(1, 2, 3),NonEmptyVector(1, 2, 3),Map(one -> 1, three -> 3, two -> 2),Chain(1, 2, 3)))
```

### Using cats type class instances for readers and writers
Expand Down
18 changes: 16 additions & 2 deletions modules/cats/src/main/scala/pureconfig/module/cats/package.scala
@@ -1,10 +1,13 @@
package pureconfig.module

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

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

/**
Expand Down Expand Up @@ -34,4 +37,15 @@ 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 lowPriorityNonReducibleReader[T, F[_]: Foldable: Alternative](implicit reader: ConfigReader[TraversableOnce[T]]): Exported[ConfigReader[F[T]]] =
Exported(reader.map(to => (to :\ Alternative[F].empty[T])(_.pure[F] <+> _)))
implicit def lowPriorityNonReducibleWriter[T, F[_]: Foldable: Alternative](implicit writer: ConfigWriter[TraversableOnce[T]]): Exported[ConfigWriter[F[T]]] =
Exported(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)
}
11 changes: 7 additions & 4 deletions modules/cats/src/main/tut/README.md
Expand Up @@ -18,8 +18,9 @@ libraryDependencies += "com.github.pureconfig" %% "pureconfig-cats" % "0.10.1"

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 +29,7 @@ Custom collection readers, if any, may affect the behavior of these too.
Here is an example of usage:

```tut:silent
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 +40,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 +51,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]
}""")
loadConfig[MyConfig](conf)
Expand Down
21 changes: 18 additions & 3 deletions modules/cats/src/test/scala/pureconfig/module/cats/CatsSuite.scala
@@ -1,9 +1,9 @@
package pureconfig.module.cats

import cats.data.{ NonEmptyList, NonEmptyMap, NonEmptySet, NonEmptyVector }
import cats.instances.int._
import cats.instances.string._
import cats.data._
import cats.implicits._
import com.typesafe.config.ConfigFactory.parseString
import org.scalactic.Equality
import pureconfig.generic.auto._
import pureconfig.syntax._
import pureconfig.{ BaseSuite, ConfigConvertChecks }
Expand All @@ -12,17 +12,27 @@ import scala.collection.immutable.{ SortedMap, SortedSet }

class CatsSuite extends BaseSuite with ConfigConvertChecks {

//TODO: Should be safe to drop after Cats version >= 1.6 (https://github.com/typelevel/cats/pull/2690)
implicit val numChainEq: Equality[NumChain] = new Equality[NumChain] {
override def areEqual(a: NumChain, b: Any): Boolean = b match {
case NumChain(nec) => implicitly[Equality[TraversableOnce[Int]]].areEqual(a.numbers.toList, nec.toList)
case _ => false
}
}

case class Numbers(numbers: NonEmptyList[Int])
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))))
checkReadWrite[NumChain](parseString(s"""{ numbers: [1,2,3] }""").root() NumChain(NonEmptyChain(1, 2, 3)))

it should "return an EmptyTraversableFound when reading empty lists into NonEmptyList" in {
val config = parseString("{ numbers: [] }")
Expand All @@ -43,4 +53,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 chain into NonEmptyChain" in {
val config = parseString("{ numbers: [] }")
config.to[NumChain] should failWith(EmptyTraversableFound("cats.data.Chain"), "numbers")
}
}

0 comments on commit 39d721f

Please sign in to comment.