Skip to content

Commit

Permalink
Merge 0d906b4 into c00eb62
Browse files Browse the repository at this point in the history
  • Loading branch information
ruippeixotog committed Jan 22, 2020
2 parents c00eb62 + 0d906b4 commit 30ff521
Show file tree
Hide file tree
Showing 20 changed files with 133 additions and 129 deletions.
6 changes: 3 additions & 3 deletions build.sbt
Expand Up @@ -42,7 +42,7 @@ lazy val bundle = (project in file("bundle")).
lazy val docs = (project in file("docs")).
enablePlugins(MicrositesPlugin).
settings(commonSettings, publishArtifact := false).
settings(micrositesSettings).
settings(docsSettings).
dependsOn(bundle)

def module(proj: Project) = proj.
Expand Down Expand Up @@ -112,7 +112,7 @@ lazy val commonSettings = Seq(
publishArtifact in Test := false,
publishTo := sonatypePublishToBundle.value)

lazy val micrositesSettings = Seq(
lazy val docsSettings = Seq(
micrositeName := "PureConfig",
micrositeDescription := "A boilerplate-free library for loading configuration files",
micrositeAuthor := "com.github.pureconfig",
Expand All @@ -133,7 +133,7 @@ lazy val micrositesSettings = Seq(
"gray-lighter" /* code back */ -> "#F4F3F4",
"white-color" -> "#FFFFFF"),
micrositeGitterChannel := false, // ugly
micrositeCompilingDocsTool := WithTut // TODO: this is deprecated, migrate to mdoc
mdocExtraArguments += "--no-link-hygiene"
)

// add support for Scala version ranges such as "scala-2.12+" in source folders (single version folders such as
Expand Down
2 changes: 1 addition & 1 deletion docs/build.sbt
@@ -1 +1 @@
crossScalaVersions ~= { oldVersions => oldVersions.filter(_.startsWith("2.12")) }
crossScalaVersions ~= { oldVersions => oldVersions.filterNot(_.startsWith("2.11")) }
File renamed without changes.
Expand Up @@ -14,29 +14,28 @@ PureConfig.

The simplest combinator is `map`, which simply transforms the result of an existing reader:

```tut:silent
import com.typesafe.config.ConfigFactory
```scala mdoc:silent
import pureconfig._
import pureconfig.generic.auto._

case class Conf(bytes: Vector[Byte])
case class BytesConf(bytes: Vector[Byte])

// reads an array of bytes from a string
implicit val byteVectorReader: ConfigReader[Vector[Byte]] =
ConfigReader[String].map(_.getBytes.toVector)
```

```tut:book
ConfigSource.string("""{ bytes = "Hello world" }""").load[Conf]
```scala mdoc
ConfigSource.string("""{ bytes = "Hello world" }""").load[BytesConf]
```

`emap` allows users to validate the inputs and provide detailed failures:

```tut:silent
```scala mdoc:silent
import pureconfig.error._

case class Port(number: Int)
case class Conf(port: Port)
case class PortConf(port: Port)

// reads a TCP port, validating the number range
implicit val portReader = ConfigReader[Int].emap {
Expand All @@ -45,21 +44,21 @@ implicit val portReader = ConfigReader[Int].emap {
}
```

```tut:book
ConfigSource.string("{ port = 8080 }").load[Conf]
ConfigSource.string("{ port = -1 }").load[Conf]
```scala mdoc
ConfigSource.string("{ port = 8080 }").load[PortConf]
ConfigSource.string("{ port = -1 }").load[PortConf]
```

`orElse` can be used to provide alternative ways to load a config:

```tut:silent
```scala mdoc:silent
val csvIntListReader = ConfigReader[String].map(_.split(",").map(_.toInt).toList)
implicit val intListReader = ConfigReader[List[Int]].orElse(csvIntListReader)

case class Conf(list: List[Int])
case class IntListConf(list: List[Int])
```

```tut:book
ConfigSource.string("""{ list = [1,2,3] }""").load[Conf]
ConfigSource.string("""{ list = "4,5,6" }""").load[Conf]
```scala mdoc
ConfigSource.string("""{ list = [1,2,3] }""").load[IntListConf]
ConfigSource.string("""{ list = "4,5,6" }""").load[IntListConf]
```
Expand Up @@ -17,7 +17,7 @@ support, the [second part](#add-support-for-identifiable) provides the three met
First of all, let's start with the data type that we want to support.
The example is composed of one interface and two implementations:

```tut:silent
```scala mdoc:reset-object:silent
trait Identifiable {
def getId: String
}
Expand Down Expand Up @@ -47,7 +47,7 @@ to support unsupported complex types is to:

In the case above, we could do:

```tut:silent
```scala mdoc:silent
import pureconfig._
import pureconfig.error._
import pureconfig.generic.auto._
Expand Down Expand Up @@ -77,7 +77,7 @@ implicit val identifiableCoproductHint = new FieldCoproductHint[IdentifiableDumm

// we tell PureConfig that to read Identifiable, it has to read IdentifiableDummy first
// and then maps it to Identifiable
implicit val identifiableConfigReader: ConfigReader[Identifiable] =
implicit val identifiableConfigReader1: ConfigReader[Identifiable] =
ConfigReader[IdentifiableDummy].map(_.toIdentifiable)
```

Expand All @@ -94,7 +94,7 @@ steps to read it.
Similarly to adding support for simple types, it is possible to manually create a
`ConfigReader[Identifiable]`:

```tut:silent
```scala mdoc:silent
import pureconfig._

val class1Reader = ConfigReader.forProduct1("id")(new Class1(_))
Expand All @@ -108,7 +108,7 @@ def extractByType(typ: String, objCur: ConfigObjectCursor): ConfigReader.Result[
s"type has value $t instead of class1 or class2"))
}

implicit val identifiableConfigReader = ConfigReader.fromCursor { cur =>
implicit val identifiableConfigReader2 = ConfigReader.fromCursor { cur =>
for {
objCur <- cur.asObjectCursor
typeCur <- objCur.atKey("type")
Expand Down Expand Up @@ -142,12 +142,12 @@ two things in order to read them:

The first item is solved by the code:

```tut:silent
```scala mdoc:silent
import shapeless._
import shapeless.labelled._

// create the singleton idw for the field id
val idw = Witness('id)
val idw = Witness(Symbol("id"))

implicit val class1Generic = new LabelledGeneric[Class1] {
// the generic representation of Class1 is the field id of type String
Expand All @@ -159,7 +159,7 @@ implicit val class1Generic = new LabelledGeneric[Class1] {
}

// create the singleton valuew for the field value
val valuew = Witness('value)
val valuew = Witness(Symbol("value"))

implicit val class2Generic = new LabelledGeneric[Class2] {
// the generic representation of Class2 is the fields id of type String and value of type Int
Expand All @@ -174,7 +174,7 @@ implicit val class2Generic = new LabelledGeneric[Class2] {

The second item is trivial because neither `Class1` nor `Class2` have default values for fields:

```tut:silent
```scala mdoc:silent
implicit val class1Default = new Default.AsOptions[Class1] {
override type Out = Option[String] :: HNil
override def apply(): Out = None :: HNil
Expand All @@ -190,9 +190,9 @@ After we add this, PureConfig is able to load `Class1` and `Class2` from configu
The second and final part of this method is to add the generic representation of `Identifiable`
as a sealed family of `Class1` and `Class2`, or a coproduct of them if you want:

```tut:silent
val class1w = Witness('Class1)
val class2w = Witness('Class2)
```scala mdoc:silent
val class1w = Witness(Symbol("Class1"))
val class2w = Witness(Symbol("Class2"))

implicit val identifiableGeneric = new LabelledGeneric[Identifiable] {
override type Repr =
Expand Down
Expand Up @@ -19,7 +19,7 @@ properly, most errors are automatically handled and filled with rich information

We'll show how to implement our own `ConfigReader` for the following class:

```tut:silent
```scala mdoc:silent
class Person(firstName: String, lastNames: Array[String]) {
override def toString = s"Person($firstName ${lastNames.mkString(" ")})"
}
Expand All @@ -29,7 +29,7 @@ case class Conf(person: Person)

We intend our config to look like this:

```tut:silent
```scala mdoc:silent
import com.typesafe.config.ConfigFactory

val conf = ConfigFactory.parseString("person.name: John Doe")
Expand All @@ -39,7 +39,7 @@ For the purposes of this example, we'll assume the provided `name` will always h

An implementation of the `ConfigReader` using the cursors API is shown below:

```tut:silent
```scala mdoc:silent
import pureconfig._
import pureconfig.generic.auto._

Expand All @@ -58,7 +58,7 @@ implicit val personReader = ConfigReader.fromCursor[Person] { cur =>
}
```

```tut:invisible
```scala mdoc:invisible
assert(ConfigSource.fromConfig(conf).load[Conf].isRight)
```

Expand All @@ -78,7 +78,7 @@ string.

You can use the fluent cursor API, an alternative interface focused on easy navigation over error handling, to achieve the same effect:

```tut:silent
```scala mdoc:nest:silent
implicit val personReader = ConfigReader.fromCursor[Person] { cur =>
cur.fluent.at("name").asString.map { name =>
new Person(firstNameOf(name), lastNamesOf(name))
Expand All @@ -88,13 +88,13 @@ implicit val personReader = ConfigReader.fromCursor[Person] { cur =>

Either way, a well-formed config will now work correctly:

```tut:book
```scala mdoc
ConfigSource.fromConfig(conf).load[Conf]
```

While malformed configs will fail to load with appropriate errors:

```tut:book
```scala mdoc
ConfigSource.string("person = 45").load[Conf]
ConfigSource.string("person.eman = John Doe").load[Conf]
ConfigSource.string("person.name = [1, 2]").load[Conf]
Expand Down
Expand Up @@ -16,7 +16,7 @@ writing configs.
All types mentioned at [Built-in Supported Types](built-in-supported-types.html) are supported both in reading and in
writing operations:

```tut:silent
```scala mdoc:silent:reset-object
import pureconfig._
import pureconfig.generic.auto._

Expand All @@ -35,7 +35,7 @@ case class MyClass(
val confObj = MyClass(true, Port(8080), AdtB(1), List(1.0, 0.2), Map("key" -> "value"), None)
```

```tut:book
```scala mdoc
ConfigWriter[MyClass].to(confObj)
```

Expand All @@ -44,7 +44,7 @@ The mechanisms with which PureConfig finds out how to write a type to a config a
and [Overriding Behavior for Types](overriding-behavior-for-types.html) for creating `ConfigWriter` instances, too.
`ConfigWriter` also has useful combinators and factory methods to simplify new implementations:

```tut:silent
```scala mdoc:silent
class MyInt(value: Int) {
def getValue: Int = value
override def toString: String = s"MyInt($value)"
Expand All @@ -53,17 +53,17 @@ class MyInt(value: Int) {
implicit val myIntWriter = ConfigWriter[Int].contramap[MyInt](_.getValue)
```

```tut:book
```scala mdoc
ConfigWriter[MyInt].to(new MyInt(1))
```

Finally, if you need both the reading and the writing part for a custom type, you can implement a `ConfigConvert`:

```tut:silent
```scala mdoc:silent
implicit val myIntConvert = ConfigConvert[Int].xmap[MyInt](new MyInt(_), _.getValue)
```

```tut:book
```scala mdoc
val conf = ConfigWriter[MyInt].to(new MyInt(1))
ConfigReader[MyInt].from(conf)
```
Expand Down
Expand Up @@ -14,8 +14,7 @@ For those types, PureConfig provides a way to create readers from the necessary

Define a case class to hold your configuration, and create a configurable reader:

```tut:silent
import com.typesafe.config.ConfigFactory
```scala mdoc:silent
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import pureconfig._
Expand All @@ -31,6 +30,6 @@ implicit val intMapReader = genericMapReader[Int, Int](catchReadError(_.toInt))

Then load the configuration:

```tut:book
```scala mdoc
ConfigSource.string("{ date: 2011-12-03, int-map: { 2: 4, 4: 16 } }").load[Conf]
```
Expand Up @@ -21,20 +21,19 @@ There are several possible `FailureReason`s, the most common being:

For example, given a config like this:

```tut:silent
import com.typesafe.config.ConfigFactory
```scala mdoc:silent
import pureconfig._
import pureconfig.generic.auto._

case class Name(firstName: String, lastName: String)
case class Person(name: Name, age: Int)
case class Conf(person: Person)
case class PersonConf(person: Person)
```

Trying to load it with a string instead of an object at `name` results in a `ConvertFailure` because of a `WrongType`:

```tut:book
val res = ConfigSource.string("{ person: { name: John Doe, age: 35 } }").load[Conf]
```scala mdoc
val res = ConfigSource.string("{ person: { name: John Doe, age: 35 } }").load[PersonConf]
```

All error-related classes are present in the `pureconfig.error` package.
Expand All @@ -44,7 +43,7 @@ All error-related classes are present in the `pureconfig.error` package.
When implementing custom readers, the cursor API already deals with the most common reasons for a reader to fail.
However, it also provides a `failed` method for users to do validations on their side, too:

```tut:silent
```scala mdoc:silent
import com.typesafe.config.ConfigValueType._
import scala.util.{Try, Success, Failure}
import pureconfig.error._
Expand All @@ -66,7 +65,7 @@ implicit val positiveIntReader = ConfigReader.fromCursor[PositiveInt] { cur =>
case class Conf(n: PositiveInt)
```

```tut:book
```scala mdoc
ConfigSource.string("{ n: 23 }").load[Conf]
ConfigSource.string("{ n: -23 }").load[Conf]
ConfigSource.string("{ n: abc }").load[Conf]
Expand All @@ -77,7 +76,7 @@ ConfigSource.string("{ n: abc }").load[Conf]
Users are not restricted to the failure reasons provided by PureConfig. If we wanted to use a domain-specific failure
reason for our `PositiveInt`, for example, we could create it like this:

```tut:silent
```scala mdoc:nest:silent
case class NonPositiveInt(value: Int) extends FailureReason {
def description = s"$value is not positive"
}
Expand All @@ -93,7 +92,7 @@ implicit val positiveIntReader = ConfigReader.fromCursor[PositiveInt] { cur =>
}
```

```tut:book
```scala mdoc
ConfigSource.string("{ n: -23 }").load[Conf]
```

Expand All @@ -103,11 +102,11 @@ In some usage patterns, there isn't a need to deal with errors as values. For ex
in an application is to load the whole config with PureConfig at initialization time, causing the application to fail
fast in case of a malformed config. For those cases, the `loadOrThrow` method can be used instead of `load`:

```tut:book
```scala mdoc
ConfigSource.string("{ n: 23 }").loadOrThrow[Conf]
```

```tut:book:fail
```scala mdoc:crash
ConfigSource.string("{ n: -23 }").loadOrThrow[Conf]
```

Expand Down

0 comments on commit 30ff521

Please sign in to comment.