Skip to content

Commit

Permalink
Merge c7f5d31 into 8fea3ba
Browse files Browse the repository at this point in the history
  • Loading branch information
ruippeixotog committed Jan 7, 2019
2 parents 8fea3ba + c7f5d31 commit 4c51b19
Show file tree
Hide file tree
Showing 36 changed files with 202 additions and 179 deletions.
5 changes: 4 additions & 1 deletion README.md
Expand Up @@ -71,9 +71,12 @@ Finally, load the configuration:

```scala
pureconfig.loadConfig[MyClass]
// res0: Either[pureconfig.error.ConfigReaderFailures,MyClass] = Right(MyClass(true,Port(8080),AdtB(1),List(1.0, 0.2),Map(key -> value),None))
// res0: pureconfig.ConfigReader.Result[MyClass] = Right(MyClass(true,Port(8080),AdtB(1),List(1.0, 0.2),Map(key -> value),None))
```

`ConfigReader.Result[MyClass]` is just an alias for `Either[ConfigReaderFailures, MyClass]`, so you can handle it just like you
would handle an `Either` value.

The various `loadConfig` methods defer to Typesafe Config's
[`ConfigFactory`](https://lightbend.github.io/config/latest/api/com/typesafe/config/ConfigFactory.html) to
select where to load the config files from. Typesafe Config has [well-documented rules for configuration
Expand Down
5 changes: 4 additions & 1 deletion bundle/src/main/tut/README.md
Expand Up @@ -71,9 +71,12 @@ Finally, load the configuration:

```scala
pureconfig.loadConfig[MyClass]
// res0: Either[pureconfig.error.ConfigReaderFailures,MyClass] = Right(MyClass(true,Port(8080),AdtB(1),List(1.0, 0.2),Map(key -> value),None))
// res0: pureconfig.ConfigReader.Result[MyClass] = Right(MyClass(true,Port(8080),AdtB(1),List(1.0, 0.2),Map(key -> value),None))
```

`ConfigReader.Result[MyClass]` is just an alias for `Either[ConfigReaderFailures, MyClass]`, so you can handle it just like you
would handle an `Either` value.

The various `loadConfig` methods defer to Typesafe Config's
[`ConfigFactory`](https://lightbend.github.io/config/latest/api/com/typesafe/config/ConfigFactory.html) to
select where to load the config files from. Typesafe Config has [well-documented rules for configuration
Expand Down
@@ -1,14 +1,12 @@
package pureconfig

import pureconfig.error._

trait ProductReaders {
@inline private def combineReaderResult[B, A](
previousResult: Either[ConfigReaderFailures, A => B],
previousResult: ConfigReader.Result[A => B],
reader: ConfigReader[A],
key: String,
cursor: ConfigObjectCursor): Either[ConfigReaderFailures, B] =
ConvertHelpers.combineResults(
cursor: ConfigObjectCursor): ConfigReader.Result[B] =
ReaderResult.zipWith(
previousResult,
if (reader.isInstanceOf[ReadsMissingKeys])
reader.from(cursor.atKeyOrUndefined(key))
Expand All @@ -27,9 +25,9 @@ trait ProductReaders {
final def forProduct1[B, A0](keyA0: String)(f: A0 => B)(implicit
readerA0: ConfigReader[A0]
): ConfigReader[B] = new ConfigReader[B] {
def from(cur: ConfigCursor): Either[ConfigReaderFailures, B] =
def from(cur: ConfigCursor): ConfigReader.Result[B] =
cur.asObjectCursor.right.flatMap { objCur =>
val a0Result: Either[ConfigReaderFailures, A0 => B] = Right(f)
val a0Result: ConfigReader.Result[A0 => B] = Right(f)
val a1Result = combineReaderResult(a0Result, readerA0, keyA0, objCur)
a1Result
}
Expand All @@ -46,9 +44,9 @@ trait ProductReaders {
final def forProduct1[B, [#A0#]]([#keyA0: String#])(f: ([#A0#]) => B)(implicit
[#readerA0: ConfigReader[A0]#]
): ConfigReader[B] = new ConfigReader[B] {
def from(cur: ConfigCursor): Either[ConfigReaderFailures, B] =
def from(cur: ConfigCursor): ConfigReader.Result[B] =
cur.asObjectCursor.right.flatMap { objCur =>
val a##0Result: Either[ConfigReaderFailures, [#A0# => ] => B] = Right(f.curried)
val a##0Result: ConfigReader.Result[[#A0# => ] => B] = Right(f.curried)
[#val a1Result = combineReaderResult(a0Result, readerA0, keyA0, objCur)#
]
a1Result
Expand Down
17 changes: 7 additions & 10 deletions core/src/main/scala/pureconfig/CollectionReaders.scala
Expand Up @@ -4,9 +4,6 @@ import scala.collection.generic.CanBuildFrom
import scala.collection.mutable
import scala.language.higherKinds

import pureconfig.ConvertHelpers._
import pureconfig.error._

/**
* A marker trait signaling that a `ConfigReader` accepts missing (undefined) values.
*
Expand All @@ -23,7 +20,7 @@ trait CollectionReaders {

implicit def optionReader[T](implicit conv: Derivation[ConfigReader[T]]): ConfigReader[Option[T]] =
new ConfigReader[Option[T]] with ReadsMissingKeys {
override def from(cur: ConfigCursor): Either[ConfigReaderFailures, Option[T]] = {
override def from(cur: ConfigCursor): ConfigReader.Result[Option[T]] = {
if (cur.isUndefined || cur.isNull) Right(None)
else conv.value.from(cur).right.map(Some(_))
}
Expand All @@ -34,23 +31,23 @@ trait CollectionReaders {
configConvert: Derivation[ConfigReader[T]],
cbf: CanBuildFrom[F[T], T, F[T]]) = new ConfigReader[F[T]] {

override def from(cur: ConfigCursor): Either[ConfigReaderFailures, F[T]] = {
override def from(cur: ConfigCursor): ConfigReader.Result[F[T]] = {
cur.asListCursor.right.flatMap { listCur =>
// we called all the failures in the list
listCur.list.foldLeft[Either[ConfigReaderFailures, mutable.Builder[T, F[T]]]](Right(cbf())) {
listCur.list.foldLeft[ConfigReader.Result[mutable.Builder[T, F[T]]]](Right(cbf())) {
case (acc, valueCur) =>
combineResults(acc, configConvert.value.from(valueCur))(_ += _)
ReaderResult.zipWith(acc, configConvert.value.from(valueCur))(_ += _)
}.right.map(_.result())
}
}
}

implicit def mapReader[T](implicit reader: Derivation[ConfigReader[T]]) = new ConfigReader[Map[String, T]] {
override def from(cur: ConfigCursor): Either[ConfigReaderFailures, Map[String, T]] = {
override def from(cur: ConfigCursor): ConfigReader.Result[Map[String, T]] = {
cur.asMap.right.flatMap { map =>
map.foldLeft[Either[ConfigReaderFailures, Map[String, T]]](Right(Map())) {
map.foldLeft[ConfigReader.Result[Map[String, T]]](Right(Map())) {
case (acc, (key, valueConf)) =>
combineResults(acc, reader.value.from(valueConf)) { (map, value) => map + (key -> value) }
ReaderResult.zipWith(acc, reader.value.from(valueConf)) { (map, value) => map + (key -> value) }
}
}
}
Expand Down
44 changes: 22 additions & 22 deletions core/src/main/scala/pureconfig/ConfigCursor.scala
Expand Up @@ -56,7 +56,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the string value pointed to by this cursor if the cast can be done, `Left` with a list of
* failures otherwise.
*/
def asString: Either[ConfigReaderFailures, String] =
def asString: ConfigReader.Result[String] =
castOrFail(STRING, v => Right(v.unwrapped.asInstanceOf[String]))

/**
Expand All @@ -65,7 +65,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the boolean value pointed to by this cursor if the cast can be done, `Left` with a list of
* failures otherwise.
*/
def asBoolean: Either[ConfigReaderFailures, Boolean] =
def asBoolean: ConfigReader.Result[Boolean] =
castOrFail(BOOLEAN, v => Right(v.unwrapped.asInstanceOf[Boolean]))

/**
Expand All @@ -74,7 +74,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the long value pointed to by this cursor if the cast can be done, `Left` with a list of
* failures otherwise.
*/
def asLong: Either[ConfigReaderFailures, Long] =
def asLong: ConfigReader.Result[Long] =
castOrFail(NUMBER, _.unwrapped match {
case i: java.lang.Number if i.longValue() == i => Right(i.longValue())
case v => Left(CannotConvert(v.toString, "Long", "Unable to convert Number to Long"))
Expand All @@ -86,7 +86,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the int value pointed to by this cursor if the cast can be done, `Left` with a list of
* failures otherwise.
*/
def asInt: Either[ConfigReaderFailures, Int] =
def asInt: ConfigReader.Result[Int] =
castOrFail(NUMBER, _.unwrapped match {
case i: java.lang.Number if i.intValue() == i => Right(i.intValue())
case v => Left(CannotConvert(v.toString, "Int", "Unable to convert Number to Int"))
Expand All @@ -98,7 +98,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the short value pointed to by this cursor if the cast can be done, `Left` with a list of
* failures otherwise.
*/
def asShort: Either[ConfigReaderFailures, Short] =
def asShort: ConfigReader.Result[Short] =
castOrFail(NUMBER, _.unwrapped match {
case i: java.lang.Number if i.shortValue() == i => Right(i.shortValue())
case v => Left(CannotConvert(v.toString, "Short", "Unable to convert Number to Short"))
Expand All @@ -110,7 +110,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the double value pointed to by this cursor if the cast can be done, `Left` with a list of
* failures otherwise.
*/
def asDouble: Either[ConfigReaderFailures, Double] =
def asDouble: ConfigReader.Result[Double] =
castOrFail(NUMBER, _.unwrapped match {
case i: java.lang.Number if i.doubleValue() == i => Right(i.doubleValue())
case v => Left(CannotConvert(v.toString, "Double", "Unable to convert Number to Double"))
Expand All @@ -122,7 +122,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the float value pointed to by this cursor if the cast can be done, `Left` with a list of
* failures otherwise.
*/
def asFloat: Either[ConfigReaderFailures, Float] =
def asFloat: ConfigReader.Result[Float] =
castOrFail(NUMBER, _.unwrapped match {
case i: java.lang.Number if i.floatValue() == i => Right(i.floatValue())
case v => Left(CannotConvert(v.toString, "Float", "Unable to convert Number to Float"))
Expand All @@ -134,7 +134,7 @@ sealed trait ConfigCursor {
* @return a `Right` with this cursor as a list cursor if the cast can be done, `Left` with a list of failures
* otherwise.
*/
def asListCursor: Either[ConfigReaderFailures, ConfigListCursor] =
def asListCursor: ConfigReader.Result[ConfigListCursor] =
castOrFail(LIST, v => Right(v.asInstanceOf[ConfigList])).right.map(ConfigListCursor(_, pathElems))

/**
Expand All @@ -143,7 +143,7 @@ sealed trait ConfigCursor {
* @return a `Right` with the list pointed to by this cursor if the cast can be done, `Left` with a list of failures
* otherwise.
*/
def asList: Either[ConfigReaderFailures, List[ConfigCursor]] =
def asList: ConfigReader.Result[List[ConfigCursor]] =
asListCursor.right.map(_.list)

/**
Expand All @@ -152,7 +152,7 @@ sealed trait ConfigCursor {
* @return a `Right` with this cursor as an object cursor if it points to an object, `Left` with a list of failures
* otherwise.
*/
def asObjectCursor: Either[ConfigReaderFailures, ConfigObjectCursor] =
def asObjectCursor: ConfigReader.Result[ConfigObjectCursor] =
castOrFail(OBJECT, v => Right(v.asInstanceOf[ConfigObject])).right.map(ConfigObjectCursor(_, pathElems))

/**
Expand All @@ -161,10 +161,10 @@ sealed trait ConfigCursor {
* @return a `Right` with the map pointed to by this cursor if the cast can be done, `Left` with a list of failures
* otherwise.
*/
def asMap: Either[ConfigReaderFailures, Map[String, ConfigCursor]] =
def asMap: ConfigReader.Result[Map[String, ConfigCursor]] =
asObjectCursor.right.map(_.map)

@inline private final def atPathSegment(pathSegment: PathSegment): Either[ConfigReaderFailures, ConfigCursor] = {
@inline private final def atPathSegment(pathSegment: PathSegment): ConfigReader.Result[ConfigCursor] = {
pathSegment match {
case PathSegment.Key(k) => this.asObjectCursor.right.flatMap(_.atKey(k))
case PathSegment.Index(i) => this.asListCursor.right.flatMap(_.atIndex(i))
Expand All @@ -178,8 +178,8 @@ sealed trait ConfigCursor {
* @return a `Right` with a cursor to the config at `pathSegments` if such a config exists, a `Left` with a list of
* failures otherwise.
*/
final def atPath(pathSegments: PathSegment*): Either[ConfigReaderFailures, ConfigCursor] = {
pathSegments.foldLeft(Right(this): Either[ConfigReaderFailures, ConfigCursor]) {
final def atPath(pathSegments: PathSegment*): ConfigReader.Result[ConfigCursor] = {
pathSegments.foldLeft(Right(this): ConfigReader.Result[ConfigCursor]) {
case (soFar, segment) => soFar.right.flatMap(_.atPathSegment(segment))
}
}
Expand All @@ -191,7 +191,7 @@ sealed trait ConfigCursor {
* failures otherwise.
*/
@deprecated("Use `asListCursor` and/or `asObjectCursor` instead", "0.10.1")
def asCollectionCursor: Either[ConfigReaderFailures, Either[ConfigListCursor, ConfigObjectCursor]] = {
def asCollectionCursor: ConfigReader.Result[Either[ConfigListCursor, ConfigObjectCursor]] = {
if (isUndefined) {
failed(KeyNotFound.forKeys(path, Set()))
} else {
Expand All @@ -212,7 +212,7 @@ sealed trait ConfigCursor {
* @tparam A the returning type of the `ConfigReader`
* @return a failed `ConfigReader` result built by scoping `reason` into the context of this cursor.
*/
def failed[A](reason: FailureReason): Either[ConfigReaderFailures, A] =
def failed[A](reason: FailureReason): ConfigReader.Result[A] =
Left(ConfigReaderFailures(failureFor(reason)))

/**
Expand All @@ -238,12 +238,12 @@ sealed trait ConfigCursor {
* @tparam A the returning type of the `ConfigReader`
* @return a `ConfigReader` result built by scoping `reason` into the context of this cursor.
*/
def scopeFailure[A](result: Either[FailureReason, A]): Either[ConfigReaderFailures, A] =
def scopeFailure[A](result: Either[FailureReason, A]): ConfigReader.Result[A] =
result.left.map { reason => ConfigReaderFailures(failureFor(reason), Nil) }

private[this] def castOrFail[A](
expectedType: ConfigValueType,
cast: ConfigValue => Either[FailureReason, A]): Either[ConfigReaderFailures, A] = {
cast: ConfigValue => Either[FailureReason, A]): ConfigReader.Result[A] = {

if (isUndefined)
failed(KeyNotFound.forKeys(path, Set()))
Expand Down Expand Up @@ -348,7 +348,7 @@ case class ConfigListCursor(value: ConfigList, pathElems: List[String], offset:
* @return a `Right` with a cursor to the config at `idx` if such a config exists, a `Left` with a list of failures
* otherwise.
*/
def atIndex(idx: Int): Either[ConfigReaderFailures, ConfigCursor] = {
def atIndex(idx: Int): ConfigReader.Result[ConfigCursor] = {
atIndexOrUndefined(idx) match {
case idxCur if idxCur.isUndefined => failed(KeyNotFound.forKeys(indexKey(idx), Set()))
case idxCur => Right(idxCur)
Expand Down Expand Up @@ -389,7 +389,7 @@ case class ConfigListCursor(value: ConfigList, pathElems: List[String], offset:
value.asScala.toList.drop(offset).zipWithIndex.map { case (cv, idx) => ConfigCursor(cv, indexKey(idx) :: pathElems) }

// Avoid resetting the offset when using ConfigCursor's implementation.
override def asListCursor: Either[ConfigReaderFailures, ConfigListCursor] = Right(this)
override def asListCursor: ConfigReader.Result[ConfigListCursor] = Right(this)
}

/**
Expand Down Expand Up @@ -420,7 +420,7 @@ case class ConfigObjectCursor(value: ConfigObject, pathElems: List[String]) exte
* @return a `Right` with a cursor to the config at `key` if such a config exists, a `Left` with a list of failures
* otherwise.
*/
def atKey(key: String): Either[ConfigReaderFailures, ConfigCursor] = {
def atKey(key: String): ConfigReader.Result[ConfigCursor] = {
atKeyOrUndefined(key) match {
case keyCur if keyCur.isUndefined => failed(KeyNotFound.forKeys(key, keys))
case keyCur => Right(keyCur)
Expand Down Expand Up @@ -452,5 +452,5 @@ case class ConfigObjectCursor(value: ConfigObject, pathElems: List[String]) exte
value.asScala.toMap.map { case (key, cv) => key -> ConfigCursor(cv, key :: pathElems) }

// Avoid unnecessary cast.
override def asObjectCursor: Either[ConfigReaderFailures, ConfigObjectCursor] = Right(this)
override def asObjectCursor: ConfigReader.Result[ConfigObjectCursor] = Right(this)
}
15 changes: 11 additions & 4 deletions core/src/main/scala/pureconfig/ConfigReader.scala
Expand Up @@ -21,15 +21,15 @@ trait ConfigReader[A] {
* @param cur The cursor from which the config should be loaded
* @return either a list of failures or an object of type `A`
*/
def from(cur: ConfigCursor): Either[ConfigReaderFailures, A]
def from(cur: ConfigCursor): ConfigReader.Result[A]

/**
* Convert the given configuration into an instance of `A` if possible.
*
* @param config The configuration from which the config should be loaded
* @return either a list of failures or an object of type `A`
*/
def from(config: ConfigValue): Either[ConfigReaderFailures, A] =
def from(config: ConfigValue): ConfigReader.Result[A] =
from(ConfigCursor(config, Nil))

/**
Expand Down Expand Up @@ -120,6 +120,13 @@ trait ConfigReader[A] {
*/
object ConfigReader extends BasicReaders with CollectionReaders with ProductReaders with ExportedReaders {

/**
* The type of most config PureConfig reading methods.
*
* @tparam A the type of the result
*/
type Result[A] = Either[ConfigReaderFailures, A]

def apply[A](implicit reader: Derivation[ConfigReader[A]]): ConfigReader[A] = reader.value

/**
Expand All @@ -129,7 +136,7 @@ object ConfigReader extends BasicReaders with CollectionReaders with ProductRead
* @tparam A the type of the objects readable by the returned reader
* @return a `ConfigReader` for reading objects of type `A` using `fromF`.
*/
def fromCursor[A](fromF: ConfigCursor => Either[ConfigReaderFailures, A]) = new ConfigReader[A] {
def fromCursor[A](fromF: ConfigCursor => ConfigReader.Result[A]) = new ConfigReader[A] {
def from(cur: ConfigCursor) = fromF(cur)
}

Expand All @@ -140,7 +147,7 @@ object ConfigReader extends BasicReaders with CollectionReaders with ProductRead
* @tparam A the type of the objects readable by the returned reader
* @return a `ConfigReader` for reading objects of type `A` using `fromF`.
*/
def fromFunction[A](fromF: ConfigValue => Either[ConfigReaderFailures, A]) =
def fromFunction[A](fromF: ConfigValue => ConfigReader.Result[A]) =
fromCursor(fromF.compose(_.value))

def fromString[A](fromF: String => Either[FailureReason, A]): ConfigReader[A] =
Expand Down
13 changes: 5 additions & 8 deletions core/src/main/scala/pureconfig/ConvertHelpers.scala
Expand Up @@ -11,15 +11,12 @@ import pureconfig.error._
*/
trait ConvertHelpers {

def combineResults[A, B, C](first: Either[ConfigReaderFailures, A], second: Either[ConfigReaderFailures, B])(f: (A, B) => C): Either[ConfigReaderFailures, C] =
(first, second) match {
case (Right(a), Right(b)) => Right(f(a, b))
case (Left(aFailures), Left(bFailures)) => Left(aFailures ++ bFailures)
case (_, l: Left[_, _]) => l.asInstanceOf[Left[ConfigReaderFailures, Nothing]]
case (l: Left[_, _], _) => l.asInstanceOf[Left[ConfigReaderFailures, Nothing]]
}
@deprecated("Use `ReaderResult.zipWith` instead", "0.10.2")
def combineResults[A, B, C](first: ConfigReader.Result[A], second: ConfigReader.Result[B])(f: (A, B) => C): ConfigReader.Result[C] =
ReaderResult.zipWith(first, second)(f)

def fail[A](failure: ConfigReaderFailure): Either[ConfigReaderFailures, A] = Left(ConfigReaderFailures(failure))
@deprecated("Use `ReaderResult.fail` instead", "0.10.2")
def fail[A](failure: ConfigReaderFailure): ConfigReader.Result[A] = Left(ConfigReaderFailures(failure))

private[pureconfig] def toResult[A, B](f: A => B): A => Either[FailureReason, B] =
v => tryToEither(Try(f(v)))
Expand Down

0 comments on commit 4c51b19

Please sign in to comment.