diff --git a/build.sbt b/build.sbt
index 19952060..6032ab8e 100644
--- a/build.sbt
+++ b/build.sbt
@@ -106,7 +106,7 @@ lazy val refined =
crossProject(JSPlatform, JVMPlatform)
.in(file("modules/refined"))
.settings(moduleName := "ciris-refined", name := "Ciris refined")
- .settings(libraryDependencies += "eu.timepit" %%% "refined" % "0.8.7")
+ .settings(libraryDependencies += "eu.timepit" %%% "refined" % refinedVersion)
.settings(scalaSettings)
.settings(testSettings)
.jsSettings(jsModuleSettings)
@@ -236,13 +236,13 @@ lazy val docs = project
s"""
|This is the API documentation for [[https://cir.is Ciris]]: lightweight, extensible, and validated configuration loading in Scala.
|The documentation is kept up-to-date with new releases, currently documenting release [[https://github.com/vlovgr/ciris/releases/tag/v$version v$version]] on Scala $scalaTargetVersion.
- |Note that the API documentation targets the JVM, and there may be differences on Scala.js and Scala Native.
+ |Please note that the documentation targets the JVM, and there may be differences on Scala.js and Scala Native.
|
|Ciris is divided into the following set of modules.
|
| - The [[ciris.cats cats]] module integrates with [[https://github.com/typelevel/cats cats]] for typeclasses and typeclass instances.
| - The [[ciris.cats.effect cats-effect]] module integrates with [[https://github.com/typelevel/cats-effect cats-effect]] for typeclasses for effect types.
- | - The [[ciris core]] module provides basic functionality and support for reading standard library types.
+ | - The [[ciris core]] module provides basic functionality and support for standard library types.
| - The [[ciris.enumeratum enumeratum]] module integrates with [[https://github.com/lloydmeta/enumeratum enumeratum]] to be able to read enumerations.
| - The [[ciris.generic generic]] module uses [[https://github.com/milessabin/shapeless shapeless]] to be able to read products and coproducts.
| - The [[ciris.refined refined]] module integrates with [[https://github.com/fthomas/refined refined]] to be able to read refinement types.
@@ -255,6 +255,10 @@ lazy val docs = project
IO.write(target, content)
target
},
+ libraryDependencies ++= Seq(
+ "org.typelevel" %% "kittens" % "1.0.0-RC3",
+ "eu.timepit" %% "refined-cats" % refinedVersion
+ ),
scalacOptions --= Seq("-Xlint", "-Ywarn-unused", "-Ywarn-unused-import"),
unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject -- inProjects(noDocumentationModules: _*),
siteSubdirName in ScalaUnidoc := micrositeDocumentationUrl.value,
@@ -439,7 +443,6 @@ generateReadme in ThisBuild := {
val source = IO.read((tutTargetDirectory in docs).value / "index.md")
val readme =
source
- .replaceAll("""\n(#+) ]+>(.+)<\/a>""", "\n$1 $2") // Remove header links
.replaceAll("^\\s*---[^(---)]*---\\s*", "") // Remove metadata
val target = (baseDirectory in ciris).value / "readme.md"
IO.write(target, readme)
@@ -458,10 +461,9 @@ updateReadme in ThisBuild := {
val generateContributing = taskKey[File]("Generates the contributing guide")
generateContributing in ThisBuild := {
(tut in docs).value
- val source = IO.read((tutTargetDirectory in docs).value / "docs" / "contributing" / "readme.md")
+ val source = IO.read((tutTargetDirectory in docs).value / "docs" / "contributing.md")
val contributing =
source
- .replaceAll("""\n(#+) ]+>(.+)<\/a>""", "\n$1 $2") // Remove header links
.replaceAll("^\\s*---[^(---)]*---\\s*", "") // Remove metadata
val target = (baseDirectory in ciris).value / "contributing.md"
IO.write(target, contributing)
@@ -652,3 +654,5 @@ addCommandsAlias("validateDocs", List("docTests", "docs/unidoc", "docs/tut"))
lazy val scalaTestVersion = "3.0.5"
lazy val scalaCheckVersion = "1.13.5"
+
+lazy val refinedVersion = "0.8.7"
diff --git a/docs/src/main/resources/microsite/css/custom.css b/docs/src/main/resources/microsite/css/custom.css
index 7485e38c..65d08d18 100644
--- a/docs/src/main/resources/microsite/css/custom.css
+++ b/docs/src/main/resources/microsite/css/custom.css
@@ -6,6 +6,7 @@ a:hover, a:focus {
blockquote {
font-size: 13px;
+ margin-top: 20px;
}
h4 {
@@ -16,8 +17,15 @@ section h1:first-of-type {
margin-top: 0;
}
-table, .highlight {
- margin-bottom: 12.5px !important;
+table {
+ margin-bottom: 12.5px;
+}
+
+pre.highlight code.hljs {
+ white-space: pre;
+ word-break: normal;
+ word-wrap: normal;
+ overflow: auto;
}
/* Home layout */
@@ -34,6 +42,15 @@ table, .highlight {
color: #f3f3f3;
}
+#site-main .use .container {
+ max-width: 845px;
+}
+
+#site-main .use .container img {
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
+
.technologies {
display: none;
}
@@ -46,6 +63,35 @@ table, .highlight {
color: rgba(243, 243, 243, 0.6);
}
+#site-main #content .header-link {
+ float: left;
+ font-size: 20px;
+ line-height: 1;
+ margin-left: -20px;
+ margin-top: 4px;
+ opacity: 0;
+ padding-right: 0;
+ transition: opacity 0.2s ease, color 0.2s ease;
+}
+
+#site-main #content h3 .header-link {
+ font-size: 18px;
+ margin-top: 2px;
+}
+
+#site-main #content h4 .header-link {
+ font-size: 15px;
+ margin-top: 0;
+ margin-left: -17px;
+}
+
+#site-main #content h1:hover .header-link,
+#site-main #content h2:hover .header-link,
+#site-main #content h3:hover .header-link,
+#site-main #content h4:hover .header-link {
+ opacity: 1;
+}
+
/* Docs layout */
.sidebar-nav > li > a:hover {
@@ -73,8 +119,40 @@ table, .highlight {
display: none;
}
-#page-content-wrapper section a:hover, #page-content-wrapper section a:focus {
+#page-content-wrapper section {
+ max-width: 845px;
+}
+
+#page-content-wrapper .nav {
+ max-width: 875px;
+ margin-right: 30px;
+}
+
+#page-content-wrapper section p,
+#page-content-wrapper section ul {
+ text-align: justify;
+ hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+}
+
+#page-content-wrapper section p code,
+#page-content-wrapper section ul li code {
+ white-space: nowrap;
+}
+
+#page-content-wrapper section > *:last-child { margin-bottom: 0; }
+#page-content-wrapper section > *:last-child > .highlight { margin-bottom: 0; }
+#page-content-wrapper section > .footnotes:last-child > ol { margin-bottom: 0; }
+#page-content-wrapper section > .footnotes:last-child > ol > li:last-child > p:last-child { margin-bottom: 0; }
+
+#page-content-wrapper section a:hover,
+#page-content-wrapper section a:focus {
text-decoration: underline;
text-decoration-skip: ink;
-webkit-text-decoration-skip: ink;
}
+
+section > ul + .highlighter-rouge {
+ margin-left: 40px;
+}
diff --git a/docs/src/main/resources/microsite/data/menu.yml b/docs/src/main/resources/microsite/data/menu.yml
index c965d049..b6356f03 100644
--- a/docs/src/main/resources/microsite/data/menu.yml
+++ b/docs/src/main/resources/microsite/data/menu.yml
@@ -1,17 +1,45 @@
options:
- title: Usage Basics
url: docs/basics
- - title: Core Concepts
- url: docs/concepts
- - title: Modules Overview
- url: docs/modules
- - title: Encoding Validation
- url: docs/validation
- - title: Multiple Environments
- url: docs/environments
+ - title: Configuration Topics
+ url: docs/topics
+ nested_options:
+ - title: Encoding Validation
+ url: docs/validation
+ - title: Multiple Environments
+ url: docs/environments
+ - title: Logging Configurations
+ url: docs/logging
- title: Configuration Sources
url: docs/sources
- - title: Custom Decoders
+ nested_options:
+ - title: Current Supported Sources
+ url: docs/supported-sources
+ - title: Supporting New Sources
+ url: docs/supporting-new-sources
+ - title: Configuration Decoders
url: docs/decoders
+ nested_options:
+ - title: Current Supported Types
+ url: docs/supported-types
+ - title: Supporting New Types
+ url: docs/supporting-new-types
+ - title: Modules Overview
+ url: docs/modules
+ nested_options:
+ - title: Cats Module
+ url: docs/cats-module
+ - title: Cats Effect Module
+ url: docs/cats-effect-module
+ - title: Enumeratum Module
+ url: docs/enumeratum-module
+ - title: Generic Module
+ url: docs/generic-module
+ - title: Refined Module
+ url: docs/refined-module
+ - title: Spire Module
+ url: docs/spire-module
+ - title: Squants Module
+ url: docs/squants-module
- title: Contributing Guide
url: docs/contributing
diff --git a/docs/src/main/resources/microsite/js/custom.js b/docs/src/main/resources/microsite/js/custom.js
index d4276f48..2cfe412a 100644
--- a/docs/src/main/resources/microsite/js/custom.js
+++ b/docs/src/main/resources/microsite/js/custom.js
@@ -1,2 +1,36 @@
// Cleanup footer credits
$('#site-footer .row:last p').html('Website built with sbt-microsites by 47 Degrees')
+
+// Linkify main page
+jQuery(document).ready(function() {
+ linkifyAllLevels("#site-main #content");
+});
+
+var anchorForId = function (id) {
+ var anchor = document.createElement("a");
+ anchor.className = "header-link";
+ anchor.href = "#" + id;
+ anchor.innerHTML = "";
+ return anchor;
+};
+
+var linkifyAnchors = function (level, containingElement) {
+ var headers = containingElement.getElementsByTagName("h" + level);
+ for (var h = 0; h < headers.length; h++) {
+ var header = headers[h];
+
+ if (typeof header.id !== "undefined" && header.id !== "") {
+ header.appendChild(anchorForId(header.id));
+ }
+ }
+};
+
+var linkifyAllLevels = function (blockSelector) {
+ var contentBlock = document.querySelector(blockSelector);
+ if (!contentBlock) {
+ return;
+ }
+ for (var level = 1; level <= 4; level++) {
+ linkifyAnchors(level, contentBlock);
+ }
+};
diff --git a/docs/src/main/tut/docs/basics.md b/docs/src/main/tut/docs/basics.md
new file mode 100644
index 00000000..bcb14934
--- /dev/null
+++ b/docs/src/main/tut/docs/basics.md
@@ -0,0 +1,317 @@
+---
+layout: docs
+title: "Usage Basics"
+position: 1
+permalink: /docs/basics
+---
+
+```tut:invisible
+val tempFile = {
+ val file = java.io.File.createTempFile("temp-", ".tmp")
+ file.deleteOnExit()
+
+ val writer = new java.io.FileWriter(file)
+ try {
+ writer.write("1234 ")
+ } finally {
+ writer.close()
+ }
+
+ file
+}
+```
+
+# Usage Basics
+Ciris is a _configuration as code_ library for compile-time safe configurations. This means that your configuration models and data is part of your code, in contrast to configuration files (and libraries like [Lightbend Config](https://github.com/lightbend/config)), where the configuration data resides in configuration files with specialized syntax. Writing configurations as code is most often an easy, safe, and secure alternative to configuration files, suitable in situations where it's easy to change and deploy software. But before we begin to explore Ciris and configurations as code, let's take a brief moment to discuss why and when you might consider configuration files, and when configurations as code could be a better fit.
+
+**Consider *configuration files* in the following situation.**
+- _You want to be able to change most configuration values without releasing a new version of the software._ Often these changes are made separate to the version control repository of your software. This can be desirable if it's difficult to release and deploy software, or if you're not sure in which environments the software will run. End-users or stakeholders might need to be able to configure certain aspects of your software, and recompiling the software to make configuration changes is not a viable alternative.
+
+**Consider *configuration as code* in the following situations.**
+- _You want to load configuration values from various sources in a uniform way._ With configuration as code, and Ciris specifically, you can load values from, for example, both environment variables and vault services in the same way. With configuration files, you might still need to fetch secret values from a vault service as a separate ad-hoc step.
+- _You want a single place for your configuration, including multiple environments, while avoiding duplicated values._ Configuration as code gives you the ability to deal with [multiple environments](/docs/environments) explicitly, making it clear when and what is different across environments, while avoiding any sort of duplication of configuration values.
+- _You want to check that the constant values in your configuration are valid at compile-time._ Constant configuration values are literals in code, and will therefore be checked at compile-time. This is especially powerful when used together with [refinement types](/docs/validation). This means you no longer have to rely on tests to confirm that your default configuration values are valid and useable.
+- _You want unused configuration values to be easy to spot._ With configuration as code, unused configuration values are dead code, making it easy to spot when certain values are not being used anymore. In contrast with configuration files, unused configuration values are often harder to spot, and get noticed first much later on.
+- _You want to avoid having to use a separate configuration syntax, and would rather just write code._ There's no need to learn and use a different syntax, and there is no need to have a library for parsing that configuration file syntax. Having configurations as code also means you're able to refactor your configurations with more confidence.
+- _You want a flexible configuration loading process, without being limited to a configuration syntax._ Due to the limited nature of configuration files and their syntax, some configuration loading is more inherently difficult, or even impossible, to express. With Ciris and configuration as code, you have more flexibility around configuration loading.
+
+While it's possible to not use any libraries when writing configurations as code, loading values from the environment typically means dealing with: different environments and configuration sources, type conversions, error handling, and validation. This is where Ciris comes in: a small library, dependency-free at its core, helping you to deal with all of that.
+
+## Configuration Values
+Ciris includes functions [`env`][env] (for reading environment variables), [`prop`][prop] (for reading system properties), and [`file`][file] and [`fileWithName`][fileWithName] (for reading file contents). In a similar fashion, if you would be using any of the external libraries like [ciris-kubernetes][ciris-kubernetes] or [ciris-aws-ssm][ciris-aws-ssm], they respectively provide functions `secret` (for reading Kubernetes secrets) and `param` (for reading AWS SSM parameters). These functions have in common that they accept a type to which to convert the value, for example `String` or `Int`, and then the key to read (or in case of [`file`][file] and [`fileWithName`][fileWithName], the file which contents should be read). The result is a key-value pair, represented by [`ConfigEntry`][ConfigEntry], like in the following example.
+
+```tut:book
+import ciris.{env, prop, file}
+
+// Read environment variable LANG as a String
+env[String]("LANG")
+
+// Read system property file.encoding as a String
+prop[String]("file.encoding")
+
+// Read the file, trim the file contents, and convert to Int
+file[Int](tempFile, _.trim)
+```
+
+Ciris handles errors when reading values, for example if the environment variable or file doesn't exist, or if the value couldn't be converted to the specified type. In the background, these functions are loading values from a [configuration source](/docs/sources) (represented by [`ConfigSource`][ConfigSource]) and converting the value to the specified type with a [configuration decoder](/docs/decoders) (represented by [`ConfigDecoder`][ConfigDecoder]). For a list of currently supported types, refer to the [current supported types](/docs/supported-types) section.
+
+It is also possible to optionally read values with `Option`.
+
+```tut:book
+val fileEncoding =
+ env[Option[String]]("FILE_ENCODING")
+
+// The unmodified source value is available in the entry,
+// and here we can see that the environment variable has
+// not been set
+fileEncoding.sourceValue
+
+// We get None back as the value, since the environment
+// variable has not been set
+fileEncoding.value
+```
+
+You can also use [`orElse`][orElse] to fall back to other values.
+
+```tut:book
+// Uses the value of the file.encoding system property as
+// the FILE_ENCODING environment variable has not been set
+env[String]("FILE_ENCODING").
+ orElse(prop[String]("file.encoding"))
+```
+
+When using [`orElse`][orElse], we get a [`ConfigValue`][ConfigValue] back, since we've combined the values of multiple [`ConfigEntry`][ConfigEntry]s.
+
+You can also combine [`orElse`][orElse] and [`orNone`][orNone] to fall back to other values, but not require any of them to be available.
+
+```tut:book
+env[String]("API_KEY").
+ orElse(prop[String]("api.key")).
+ orNone
+```
+
+It is also possible to read values with type [`Secret`][Secret], in order to prevent values from being output in logs.
+
+```tut:book
+import ciris.Secret
+
+prop[Secret[Option[String]]]("api.key").value
+```
+
+For more information on [`Secret`][Secret], refer to the [logging configurations](/docs/logging) section.
+
+### Suspending Effects
+Most functions we've seen so far return values wrapped in a context [`Id`][Id], which is a way to say that there is no context. Since [`Id`][Id] is defined as `type Id[A] = A`, you simply get the values without any context. However, you might have noticed that certain functions, while being safe, are not _pure_ in the sense that, if the function is called more than once with the same arguments, it might return different values. This applies to, for example, reading system properties with [`prop`][prop] (properties being mutable), and [`file`][file] and [`fileWithName`][fileWithName] (file contents may change).
+
+One way to deal with these functions not being pure, is to model the effects explicitly with _effect types_. Instead of returning the result of reading a file, for example, we merely describe _how_ to read the file, by suspending the reading of the file in a context `F[_]` for which there is a [`Sync`][Sync] instance (for example, `IO` from [cats-effect][cats-effect] with the [cats-effect module](/docs/cats-effect-module)).
+
+Ciris provides pure functions [`argF`][argF], [`envF`][envF], [`propF`][propF], and [`fileSync`][fileSync] and [`fileWithNameSync`][fileWithNameSync], which suspend the reading in a context `F[_]` for which there is a [`Sync`][Sync] instance. (Note that [`envF`][envF] is merely a convenience method which lifts the value into `F` without suspending, since environment variables are immutable.) If you're using any of the external libraries, like [ciris-kubernetes][ciris-kubernetes] or [ciris-aws-ssm][ciris-aws-ssm], they also provide pure functions which suspend reading, like `secretF` (for reading Kubernetes secrets) and `paramF` (for reading AWS SSM parameters).
+
+```tut:book
+import ciris.{propF, fileSync}
+import ciris.cats.effect._
+import cats.effect.IO
+
+// Suspend reading of system property file.encoding as a String
+propF[IO, String]("file.encoding")
+
+// Suspend reading the file, trim the file contents, and convert to Int
+fileSync[IO, Int](tempFile, _.trim)
+```
+
+## Loading Configurations
+We're now able to represent configuration entries (with [`ConfigEntry`][ConfigEntry]) and configuration values (with [`ConfigValue`][ConfigValue]) in our application, so now it's time to combine multiple values into a configuration. We'll start by modelling our configuration with nested case classes, like in the following example. If you haven't already separated your application from your configuration, now is a good time to do so, to be able to load the configuration seperately. In the example below, we're using [refinement types](/docs/refined-module) to [encode validation](/docs/validation) in the types of the configuration.
+
+```tut:silent
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.string.MatchesRegex
+import eu.timepit.refined.types.net.UserPortNumber
+import eu.timepit.refined.types.numeric.PosInt
+import eu.timepit.refined.types.string.NonEmptyString
+import eu.timepit.refined.W
+
+type ApiKey = String Refined MatchesRegex[W.`"[a-zA-Z0-9]{25,40}"`.T]
+
+final case class ApiConfig(
+ key: Secret[ApiKey],
+ port: UserPortNumber,
+ timeoutSeconds: PosInt
+)
+
+final case class Config(
+ appName: NonEmptyString,
+ api: ApiConfig
+)
+```
+
+The API key is secret, and we've wrapped it in [`Secret`][Secret] to denote that it shouldn't be included in log output. For the port, we need it to be dynamic depending on the environment, and default to a fixed port if it's not specified. In order to combine multiple configuration values, Ciris provides the [`loadConfig`][loadConfig] function, which accepts a number of configuration values ([`ConfigEntry`][ConfigEntry]s or [`ConfigValue`][ConfigValue]s) and the function with which to create the configuration.
+
+```tut:book
+import ciris.loadConfig
+import ciris.refined._
+import eu.timepit.refined.auto._
+
+val config =
+ loadConfig(
+ env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")),
+ prop[Option[UserPortNumber]]("http.port")
+ ) { (apiKey, port) =>
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse 4000
+ )
+ )
+ }
+```
+
+Note that the literal values above (the name, timeout, and default port) are validated at compile-time. If there are errors for the configuration values, Ciris will deal with them and accumulate them as [`ConfigErrors`][ConfigErrors], before finally returning an `Either[ConfigErrors, Config]`. Note that the result is wrapped in [`Id`][Id], which is to say that no context (for example, effect type) was used. We could just as well have described the configuration loading with, for example, `IO` from [cats-effect][cats-effect] instead, using `envF` and `propF`, as seen in the [suspending effects](#suspending-effects) section.
+
+```tut:book
+import ciris.{envF, propF}
+
+val configF =
+ loadConfig(
+ envF[IO, Secret[ApiKey]]("API_KEY").
+ orElse(propF[IO, Secret[ApiKey]]("api.key")),
+ propF[IO, Option[UserPortNumber]]("http.port")
+ ) { (apiKey, port) =>
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse 4000
+ )
+ )
+ }
+```
+
+At this point, we can see that both the `API_KEY` environment variable and `api.key` system property are missing, so we've gotten a [`ConfigErrors`][ConfigErrors] back. We can use the [`messages`][messages] function to retrieve error messages which are a bit more readable. Alternatively, with a syntax import, you can use [`orThrow`][orThrow] to throw an exception with the error messages, if there are any, or return the configuration if it could be loaded successfully.
+
+```tut:book
+config.left.map(_.messages)
+```
+
+```tut:book:fail
+{
+ import ciris.syntax._
+ config.orThrow()
+}
+```
+### Dynamic Configuration Loading
+Sometimes it's necessary to change how the configuration is loaded depending on some configuration value. For example, you might want to load configurations differently depending on in which environment the application is being run. You might want to use a fixed configuration in the local and testing environments, while loading secret values from a vault service in the production environment. One way to represent environments is with [enumeratum](/docs/enumeratum-module) enumerations, like in the following example. Refer to the [multiple environments](/docs/environments) section for more information.
+
+```tut:silent
+object environments {
+ import enumeratum._
+
+ sealed abstract class AppEnvironment extends EnumEntry
+
+ object AppEnvironment extends Enum[AppEnvironment] {
+ case object Local extends AppEnvironment
+ case object Testing extends AppEnvironment
+ case object Production extends AppEnvironment
+
+ val values = findValues
+ }
+}
+
+import environments._
+import AppEnvironment._
+```
+
+Ciris provides the [`withValues`][withvalues] (and [`withValue`][withValue] for a single value) function for being able to change how the configuration is loaded depending on the specified values. For example, following is an example of how to use a fixed configuration in the local and testing environments, while keeping the previously seen configuration loading in the production environment. Note that when using [`withValues`][withValues], errors for the specified values (in this case, the `APP_ENV` environment variable) means we will not continue to try to load the configuration, meaning potential further errors are not included.
+
+```tut:book
+import ciris.withValue
+import ciris.enumeratum._
+
+val config =
+ withValue(env[AppEnvironment]("APP_ENV")) {
+ case Local | Testing =>
+ loadConfig {
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = Secret("RacrqvWjuu4KVmnTG9b6xyZMTP7jnX"),
+ timeoutSeconds = 10,
+ port = 4000
+ )
+ )
+ }
+
+ case Production =>
+ loadConfig(
+ env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")),
+ prop[Option[UserPortNumber]]("http.port")
+ ) { (apiKey, port) =>
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse 4000
+ )
+ )
+ }
+ }
+```
+
+In many cases it's not necessary to have this kind of dynamic configuration loading. If you want to keep the configuration loading process the same across environments, but want to have some values be different depending on the environment, that is also possible, like in the following example. You can also mix the two approaches as you see necessary.
+
+```tut:book
+val config =
+ loadConfig(
+ env[AppEnvironment]("APP_ENV"),
+ env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")),
+ prop[Option[UserPortNumber]]("http.port")
+ ) { (environment, apiKey, port) =>
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse (environment match {
+ case Local | Testing => 4000
+ case Production => 9000
+ })
+ )
+ )
+ }
+```
+
+[enumeratum]: https://github.com/lloydmeta/enumeratum
+[ciris-kubernetes]: https://github.com/ovotech/ciris-kubernetes
+[ciris-aws-ssm]: https://github.com/ovotech/ciris-aws-ssm
+[env]: /api/ciris/index.html#env[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[prop]: /api/ciris/index.html#prop[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[file]: /api/ciris/index.html#file[Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,(java.io.File,java.nio.charset.Charset),String,Value]
+[fileWithName]: /api/ciris/index.html#fileWithName[Value](name:String,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,(java.io.File,java.nio.charset.Charset),String,Value]
+[ConfigEntry]: /api/ciris/ConfigEntry.html
+[ConfigSource]: /api/ciris/ConfigSource.html
+[ConfigDecoder]: /api/ciris/ConfigDecoder.html
+[orElse]: /api/ciris/ConfigValue.html#orElse[A>:V](that:ciris.ConfigValue[F,A]):ciris.ConfigValue[F,A]
+[ConfigValue]: /api/ciris/ConfigValue.html
+[Secret]: /api/ciris/Secret.html
+[cats-effect]: https://github.com/typelevel/cats-effect
+[loadConfig]: /api/ciris/index.html#loadConfig[F[_],A1,A2,Z](a1:ciris.ConfigValue[F,A1],a2:ciris.ConfigValue[F,A2])(f:(A1,A2)=>Z)(implicitevidence$5:ciris.api.Functor[F]):F[Either[ciris.ConfigErrors,Z]]
+[argF]: /api/ciris/index.html#argF[F[_],Value](args:IndexedSeq[String])(index:Int)(implicitevidence$3:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,Int,String,Value]
+[envF]: /api/ciris/index.html#envF[F[_],Value](key:String)(implicitevidence$1:ciris.api.Applicative[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[fileSync]: /api/ciris/index.html#fileSync[F[_],Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitevidence$1:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,(java.io.File,java.nio.charset.Charset),String,Value]
+[fileWithNameSync]: /api/ciris/index.html#fileWithNameSync[F[_],Value](name:String,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitevidence$2:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,(java.io.File,java.nio.charset.Charset),String,Value]
+[propF]: /api/ciris/index.html#propF[F[_],Value](key:String)(implicitevidence$2:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[Secret]: /api/ciris/Secret.html
+[Id]: /api/ciris/api/index.html#Id[A]=A
+[ConfigErrors]: /api/ciris/ConfigErrors.html
+[messages]: /api/ciris/ConfigErrors.html#messages:Vector[String]
+[orThrow]: /api/ciris/syntax$$EitherConfigErrorsSyntax.html#orThrow():T
+[withValues]: /api/ciris/index.html#withValues[F[_],A1,A2,Z](a1:ciris.ConfigValue[F,A1],a2:ciris.ConfigValue[F,A2])(f:(A1,A2)=>F[Either[ciris.ConfigErrors,Z]])(implicitevidence$6:ciris.api.Monad[F]):F[Either[ciris.ConfigErrors,Z]]
+[withValue]: /api/ciris/index.html#withValue[F[_],A1,Z](a1:ciris.ConfigValue[F,A1])(f:A1=>F[Either[ciris.ConfigErrors,Z]])(implicitevidence$3:ciris.api.Monad[F]):F[Either[ciris.ConfigErrors,Z]]
+[orNone]: /api/ciris/ConfigValue.html#orNone:ciris.ConfigValue[F,Option[V]]
+[Sync]: /api/ciris/api/Sync.html
diff --git a/docs/src/main/tut/docs/basics/readme.md b/docs/src/main/tut/docs/basics/readme.md
deleted file mode 100644
index c3d3756a..00000000
--- a/docs/src/main/tut/docs/basics/readme.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-layout: docs
-title: "Usage Basics"
-position: 1
-permalink: /docs/basics
----
-
-# Usage Basics
-Ciris configuration loading is done in two parts: define what to load and what to create once everything is loaded. Let's start simple by defining a configuration and loading only the necessary parts of it from the environment. If you haven't already, now is also a good time to separate your application from your configuration, so that the configuration can be loaded separately.
-
-The configuration can be modeled with nested case classes. Here we'll define a small example configuration for an HTTP service, binding at a certain port, using an API key for request authorization, and using a maximum timeout when making HTTP requests to other services.
-
-```tut:book
-import ciris._
-import scala.concurrent.duration._
-
-final case class Config(
- apiKey: Secret[String],
- timeout: Duration,
- port: Int
-)
-```
-
-In this case, the API key is a secret and we would like to load it from the environment. We wrap the type in `Secret` to denote that it shouldn't be included in log output. For the port, we need it to be dynamic depending on the environment. We can read environment variables using the `env` method and system properties using the `prop` method, both returning a `ConfigEntry`. The `loadConfig` method then accepts `ConfigEntry`s and expects a function creating the configuration using the loaded values. If there are errors while reading values, Ciris will deal with them and accumulate them as `ConfigErrors`. You can read more about `ConfigEntry` and `ConfigErrors` in [Core Concepts](/docs/concepts).
-
-```tut:book
-val config =
- loadConfig(
- env[Secret[String]]("API_KEY"), // Reads environment variable API_KEY
- prop[Option[Int]]("http.port") // Reads system property http.port
- ) { (apiKey, port) =>
- Config(
- apiKey = apiKey,
- timeout = 10 seconds,
- port = port getOrElse 4000
- )
- }
-```
-
-The values which do not need to be loaded from the environment can be written as literals. It's also possible to optionally read values (like for the port in the example above), providing defaults if they have not been specified. In this case, we forgot to specify the `API_KEY` environment variable and therefore get an appropriate error. If we want more sensible error messages, simply use the `messages` method on `ConfigErrors`.
-
-```tut:book
-config.left.map(_.messages)
-```
diff --git a/docs/src/main/tut/docs/cats-effect-module.md b/docs/src/main/tut/docs/cats-effect-module.md
new file mode 100644
index 00000000..38421e41
--- /dev/null
+++ b/docs/src/main/tut/docs/cats-effect-module.md
@@ -0,0 +1,59 @@
+---
+layout: docs
+title: "Cats Effect Module"
+permalink: /docs/cats-effect-module
+---
+
+```tut:invisible
+val tempFile = {
+ val file = java.io.File.createTempFile("temp-", ".tmp")
+ file.deleteOnExit()
+
+ val writer = new java.io.FileWriter(file)
+ try {
+ writer.write("1234 ")
+ } finally {
+ writer.close()
+ }
+
+ file
+}
+```
+
+# Cats Effect Module
+The `ciris-cats-effect` module provides effect types and effect type class instances for contexts `F[_]` from [cats-effect][cats-effect]. Effect types are useful for explicitly modelling side-effects, including [suspending effects](/docs/basics#suspending-effects) for reading configuration values. Ciris provides functions [`envF`][envF], [`propF`][propF], and [`fileSync`][fileSync] and [`fileWithNameSync`][fileWithNameSync], which suspend the reading in a context `F[_]`, for which there is a [`Sync`][Sync] instance available.
+
+```tut:book
+import ciris.{propF, fileSync}
+import ciris.cats.effect._
+import cats.effect.IO
+
+// Suspend reading of system property file.encoding as a String
+propF[IO, String]("file.encoding")
+
+// Suspend reading the file, trim the file contents, and convert to Int
+fileSync[IO, Int](tempFile, _.trim)
+```
+
+If we have a [`Sync`][Sync] instance for `F[_]`, we can take an existing [`ConfigSource`][ConfigSource] and create a new [`ConfigSource`][ConfigSource] with suspended reading using the [`suspendF`][suspendF] function. If we also need to memoize the results, there is a [`suspendMemoizeF`][suspendMemoizeF] function for that purpose.
+
+```tut:book
+import ciris.ConfigSource
+import ciris.cats.effect.syntax._
+
+ConfigSource.Properties.suspendF[IO]
+
+ConfigSource.Properties.suspendMemoizeF[IO]
+```
+
+The [supporting new sources](/docs/supporting-new-sources#suspending-effects) section contains an example of how these functions can be used.
+
+[cats-effect]: https://github.com/typelevel/cats-effect
+[envF]: /api/ciris/index.html#envF[F[_],Value](key:String)(implicitevidence$1:ciris.api.Applicative[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[fileSync]: /api/ciris/index.html#fileSync[F[_],Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitevidence$1:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,(java.io.File,java.nio.charset.Charset),String,Value]
+[fileWithNameSync]: /api/ciris/index.html#fileWithNameSync[F[_],Value](name:String,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitevidence$2:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,(java.io.File,java.nio.charset.Charset),String,Value]
+[propF]: /api/ciris/index.html#propF[F[_],Value](key:String)(implicitevidence$2:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[suspendF]: /api/ciris/ConfigSource.html#suspendF[G[_]](implicitevidence$1:ciris.api.Sync[G],implicitf:F~>G):ciris.ConfigSource[G,K,V]
+[suspendMemoizeF]: /api/ciris/cats/effect/syntax$$CatsEffectConfigSourceIdSyntax.html#suspendMemoizeF[F[_]](implicitevidence$1:ciris.api.Apply[F],implicitevidence$2:cats.effect.LiftIO[F]):ciris.ConfigSource[F,K,V]
+[Sync]: /api/ciris/api/Sync.html
+[ConfigSource]: /api/ciris/ConfigSource.html
diff --git a/docs/src/main/tut/docs/cats-module.md b/docs/src/main/tut/docs/cats-module.md
new file mode 100644
index 00000000..c0b0c013
--- /dev/null
+++ b/docs/src/main/tut/docs/cats-module.md
@@ -0,0 +1,50 @@
+---
+layout: docs
+title: "Cats Module"
+permalink: /docs/cats-module
+---
+
+# Cats Module
+The `ciris-cats` module provides type class instances for contexts `F[_]` from [cats][cats] when used together with Ciris. [`Id`][Id] is the default context, and is used when no explicit context is desired. The `ciris-cats` module is useful in cases when `F` is not [`Id`][Id], but for example `Future`, like in the following example. Note that Ciris does not provide type class instances for any other contexts than [`Id`][Id] and relies on instances for other contexts from libraries like [cats][cats].
+
+Let's define an example [`ConfigSource`][ConfigSource] which reads `Future` values.
+
+```tut:silent
+import cats.implicits._
+import ciris._
+import ciris.cats._
+import ciris.ConfigError.right
+import scala.concurrent._
+import scala.concurrent.duration._
+
+implicit val executionContext: ExecutionContext =
+ ExecutionContext.Implicits.global
+
+val source: ConfigSource[Future, String, String] = {
+ val keyType = ConfigKeyType[String]("example key")
+ ConfigSource.applyF(keyType) { key: String =>
+ Future.successful(right(key.length.toString))
+ }
+}
+```
+
+We can then load, decode, and combine values into a configuration as follows.
+
+```tut:book
+final case class Config(first: Int, second: Int)
+
+val futureConfig =
+ loadConfig(
+ source.read("firstKey").decodeValue[Int],
+ source.read("secondKey").decodeValue[Int]
+ )(Config)
+
+val config = Await.result(futureConfig, 1.second)
+```
+
+The `ciris-cats` module also provides [`Show`][Show] type class instances for [logging configurations](/docs/logging#logging-improvements).
+
+[ConfigSource]: /api/ciris/ConfigSource.html
+[Show]: https://typelevel.org/cats/typeclasses/show.html
+[cats]: https://github.com/typelevel/cats
+[Id]: /api/ciris/api/index.html#Id[A]=A
diff --git a/docs/src/main/tut/docs/concepts/readme.md b/docs/src/main/tut/docs/concepts/readme.md
deleted file mode 100644
index a7a70544..00000000
--- a/docs/src/main/tut/docs/concepts/readme.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-layout: docs
-title: "Core Concepts"
-position: 2
-permalink: /docs/concepts
----
-
-# Core Concepts
-This section explains the core concepts in Ciris, including the types `ConfigSource`, `ConfigEntry`, `ConfigValue`, `ConfigKeyType`, `ConfigDecoder`, `ConfigError`, and `ConfigErrors` and the methods `loadConfig`, `withValue`, `withValues`, `env`, `prop`, `arg`, `file`, and `fileWithName`. For [basic usage](/docs/basics), you do not need to have a complete understanding of these concepts, although it might be helpful. If you need to do some integration with Ciris, like creating a new [module](/docs/modules), defining a new [configuration source](/docs/sources), or writing a [custom decoder](/docs/decoders) to support new types, understanding these core concepts will be beneficial.
-
-## Configuration Sources
-Configuration values can be read from different sources, represented by `ConfigSource[F, K, V]`s, which are anything that can map keys of type `K` to values of type `F[V]`, like `Id[Map[Int, String]]` for example, and that return a `ConfigError` error if there was an error while reading the value (for example, when no value exists for a given key). A `ConfigSource` reads keys described by `ConfigKeyType`, which is simply a wrapper around the key type and name, for example `environment variable` and type `String`. `ConfigSource` returns a `ConfigEntry`, which is the result of reading a key, including the key itself and the `ConfigKeyType`. The abstract class `ConfigSource` is defined as follows.
-
-```scala
-abstract class ConfigSource[F[_], K, V](val keyType: ConfigKeyType[K]) {
- def read(key: K): ConfigEntry[F, K, V, V]
-}
-```
-
-Ciris provides `ConfigSource`s for environment variables, system properties, command-line arguments, and files in the core library. If you require other configuration sources, you can easily create your own. You can read more about `ConfigSource`s in the [Configuration Sources](/docs/sources) section.
-
-## Configuration Decoders
-Values of type `A` retrieved from a `ConfigSource` can be converted into another type `B` using a `ConfigDecoder[A, B]`. `ConfigDecoders`s accept a `ConfigEntry` from a source and tries to convert the `A` value into type `B`, returning a `ConfigError` error if the conversion was unsuccessful. The abstract class `ConfigDecoder` is defined as follows.
-
-```scala
-abstract class ConfigDecoder[A, B] {
- def decode[F[_]: Monad, K, S](
- entry: ConfigEntry[F, K, S, A]
- ): F[Either[ConfigError, B]]
-}
-```
-
-Ciris provides `ConfigDecoder` instances for many types in the standard library, and for third-party library types in separate modules. Modules like `ciris-generic` use [shapeless](https://github.com/milessabin/shapeless) to derive `ConfigDecoder` instances for certain types, like case classes and [value classes](http://docs.scala-lang.org/overviews/core/value-classes.html). For more information on Ciris modules, see [Modules Overview](/docs/modules). You can also easily create your own `ConfigDecoder`s, as described in the [Custom Decoders](/docs/decoders) section.
-
-## Configuration Entries
-A `ConfigEntry[F, K, S, V]` is a key-value pair retrieved from a `ConfigSource[F, K, S]` where the source value has been decoded into type `V` (normally with a `ConfigDecoder[S, V]`). `ConfigEntry` keeps the key, key type, and unmodifed source value to support sensible error messages. Ciris provides methods, like `env` and `prop`, for reading `ConfigEntry`s from environment variables, system properties, and other configuration sources. The value part of a `ConfigEntry[F, K, S, V]` is a `ConfigValue[F, V]`; `ConfigEntry` directly extends `ConfigValue`.
-
-## Loading Configurations
-The `loadConfig` and `withValues` (and `withValue`) methods allow you to combine multiple `ConfigEntry`s, while accumulating errors, and using those values to create your configuration. The difference between them is that `withValues` declares a dependency required to be able to use `loadConfig` (think `flatMap`), while `loadConfig` loads your configuration using `ConfigEntry`s. The `withValues` method is useful, for example, when dealing with [Multiple Environments](/docs/environments).
diff --git a/docs/src/main/tut/docs/contributing/readme.md b/docs/src/main/tut/docs/contributing.md
similarity index 99%
rename from docs/src/main/tut/docs/contributing/readme.md
rename to docs/src/main/tut/docs/contributing.md
index 8e09137b..e2d81d54 100644
--- a/docs/src/main/tut/docs/contributing/readme.md
+++ b/docs/src/main/tut/docs/contributing.md
@@ -1,7 +1,7 @@
---
layout: docs
title: "Contributing Guide"
-position: 8
+position: 6
permalink: /docs/contributing
---
diff --git a/docs/src/main/tut/docs/decoders.md b/docs/src/main/tut/docs/decoders.md
new file mode 100644
index 00000000..8a361667
--- /dev/null
+++ b/docs/src/main/tut/docs/decoders.md
@@ -0,0 +1,49 @@
+---
+layout: docs
+title: "Configuration Decoders"
+position: 4
+permalink: /docs/decoders
+---
+
+# Configuration Decoders
+Configuration decoders are represented in Ciris with [`ConfigDecoder`][ConfigDecoder]s and provide the ability to decode the value of a [`ConfigEntry`][ConfigEntry] to a different type, while handling errors. [`ConfigDecoder`][ConfigDecoder]s have access to the whole [`ConfigEntry`][ConfigEntry] to be able to provide sensible error messages, even though only the value is being decoded. [`ConfigDecoder`][ConfigDecoder]s generally require that a [`Monad`][Monad] instance is available for the context `F` of the [`ConfigEntry`][ConfigEntry], in order to support most decoders. Following is a simplified definition of [`ConfigDecoder`][ConfigDecoder] for reference.
+
+```tut:silent
+import ciris.{ConfigEntry, ConfigError}
+import ciris.api.Monad
+
+{
+ abstract class ConfigDecoder[A, B] {
+ def decode[F[_]: Monad, K, S](
+ entry: ConfigEntry[F, K, S, A]
+ ): F[Either[ConfigError, B]]
+ }
+}
+```
+
+Most [`ConfigDecoder`][ConfigDecoder]s provided by Ciris support decoding from `String`, but there is nothing preventing you from creating decoders for other types. [`ConfigDecoder`][ConfigDecoder] has several combinators helping you create new decoders from existing ones, and the [companion object][ConfigDecoderCompanion] of [`ConfigDecoder`][ConfigDecoder] provides several functions for helping you create new decoders. The [supporting new types](/docs/supporting-new-types) section provides more information on how to create decoders for new types. For currently support types, instead refer to the [current supported types](/docs/supported-types) section.
+
+[`ConfigDecoder`][ConfigDecoder]s are most often used indirectly via [`decodeValue`][decodeValue] on [`ConfigEntry`][ConfigEntry]. For example, if we take a look at the [`env`][env] function for reading and decoding environment variables, we'll see that it simply reads the environment variable from [`ConfigSource.Environment`][ConfigSourceEnvironment] and then decodes the value with [`decodeValue`][decodeValue].
+
+```tut:silent
+import ciris.{ConfigDecoder, ConfigSource}
+import ciris.api.Id
+
+{
+ def env[Value](key: String)(
+ implicit decoder: ConfigDecoder[String, Value]
+ ): ConfigEntry[Id, String, String, Value] = {
+ ConfigSource.Environment
+ .read(key)
+ .decodeValue[Value]
+ }
+}
+```
+
+[ConfigDecoderCompanion]: /api/ciris/ConfigDecoder$.html
+[ConfigDecoder]: /api/ciris/ConfigDecoder.html
+[ConfigEntry]: /api/ciris/ConfigEntry.html
+[Monad]: /api/ciris/api/Monad.html
+[decodeValue]: /api/ciris/ConfigEntry.html#decodeValue[A](implicitdecoder:ciris.ConfigDecoder[V,A],implicitmonad:ciris.api.Monad[F]):ciris.ConfigEntry[F,K,S,A]
+[env]: /api/ciris/index.html#env[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[ConfigSourceEnvironment]: /api/ciris/ConfigSource$.html#Environment
diff --git a/docs/src/main/tut/docs/decoders/readme.md b/docs/src/main/tut/docs/decoders/readme.md
deleted file mode 100644
index dbd2eda0..00000000
--- a/docs/src/main/tut/docs/decoders/readme.md
+++ /dev/null
@@ -1,64 +0,0 @@
----
-layout: docs
-title: "Custom Decoders"
-position: 7
-permalink: /docs/decoders
----
-
-# Custom Decoders
-Ciris already supports reading many standard library types and provides integrations with libraries like [enumeratum][enumeratum], [refined][refined], and [squants][squants]. If you're trying to load a standard library type, it is likely that it should be supported by Ciris, so please [file an issue](https://github.com/vlovgr/ciris/issues/new) or, even better, submit a pull-request. The same applies if you're trying to integrate with a library for which Ciris does not already provide a module.
-
-However, there may be cases where you need to resort to defining custom decoders for your types. For example, let's say you're dealing with a sealed `Odd` class, where you can only construct instances from an `odd` method, which accepts an `Int` and returns an `Option[Odd]`.
-
-```tut:book
-import ciris._
-
-sealed abstract case class Odd(value: Int)
-
-def odd(value: Int): Option[Odd] = {
- Option(value)
- .filter(_ % 2 == 1)
- .map(new Odd(_) {})
-}
-```
-
-We would now like to load `Odd` values using Ciris. If we try and do this straight away, it will fail during compile, saying there is no implicit `ConfigDecoder` in scope.
-
-```tut:book:fail
-env[Odd]("ODD_VALUE")
-```
-
-This means we'll have to define a custom implicit `ConfigDecoder[String, Odd]` instance. The [`ConfigDecoder`](https://cir.is/api/ciris/ConfigDecoder$.html) companion object provides some helper methods for creating `ConfigDecoder`s. In this case, we'll go one step further and define a `ConfigDecoder[A, Odd]` by relying on an existing `ConfigDecoder[A, Int]` to first decode to an `Int`. We'll then use `mapOption` to convert the `Int` to an `Odd`. Most `ConfigDecoder` methods accept a `typeName` argument which is the name of the type you're decoding. Depending on which Scala version and which platform (Scala, Scala.js, Scala Native) you run on, you may be able to use [type tags](http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html). In this case, and cases where you don't have type parameters, it's simple enough to just provide the type name.
-
-```tut:book
-implicit def oddConfigDecoder[A](
- implicit decoder: ConfigDecoder[A, Int]
-): ConfigDecoder[A, Odd] = {
- decoder.mapOption("Odd")(odd)
-}
-```
-
-We can then try to read `Odd` values from a custom [configuration source](/docs/sources).
-
-```tut:book
-val source = {
- val keyType = ConfigKeyType[String]("int key")
- ConfigSource.fromMap(keyType)(Map("a" -> "abc", "b" -> "6", "c" -> "3"))
-}
-
-val a = source.read("a").decodeValue[Odd]
-
-a.value.left.map(_.message)
-
-val b = source.read("b").decodeValue[Odd]
-
-b.value.left.map(_.message)
-
-source.read("c").decodeValue[Odd]
-```
-
-While this demonstrates how to create custom `ConfigDecoder`s, a better way to represent `Odd` values is by using the `Odd` predicate from [refined][refined]. The [Encoding Validation](/docs/validation) section has more information on the `ciris-refined` module and how you can use it to read refined types.
-
-[enumeratum]: https://github.com/lloydmeta/enumeratum
-[refined]: https://github.com/fthomas/refined
-[squants]: https://github.com/typelevel/squants
diff --git a/docs/src/main/tut/docs/enumeratum-module.md b/docs/src/main/tut/docs/enumeratum-module.md
new file mode 100644
index 00000000..ed81862c
--- /dev/null
+++ b/docs/src/main/tut/docs/enumeratum-module.md
@@ -0,0 +1,57 @@
+---
+layout: docs
+title: "Enumeratum Module"
+permalink: /docs/enumeratum-module
+---
+
+# Enumeratum Module
+The `ciris-enumeratum` module provides support for decoding [enumeratum][enumeratum] enumerations. Enumerations are especially useful for dealing with [multiple environments](/docs/environments). Let's take a look at an example, where we define an enumeration and try to load values of that enumeration. Enumeratum provides mixins, like `Lowercase` below, which can be used to customize the name of our enumeration values, and in turn, what values we will be able to decode.
+
+```tut:silent
+import enumeratum._
+import enumeratum.EnumEntry._
+
+object environments {
+ sealed abstract class AppEnvironment extends EnumEntry with Lowercase
+
+ object AppEnvironment extends Enum[AppEnvironment] {
+ case object Local extends AppEnvironment
+ case object Testing extends AppEnvironment
+ case object Production extends AppEnvironment
+
+ val values = findValues
+ }
+}
+```
+
+We also define a custom [configuration source](/docs/sources), holding some values we can attempt to decode.
+
+```tut:silent
+import ciris.{ConfigKeyType, ConfigSource}
+import ciris.enumeratum._
+import environments._
+
+val source = {
+ val keyType = ConfigKeyType[String]("enumeratum key")
+ ConfigSource.fromMap(keyType)(Map(
+ "localEnv" -> "local",
+ "testingEnv" -> "testing",
+ "TestingEnv" -> "Testing",
+ "invalidEnv" -> "invalid"
+ ))
+}
+```
+
+Finally, we can read values from the source and decode them as enumeration values.
+
+```tut:book
+source.read("localEnv").decodeValue[AppEnvironment]
+
+source.read("testingEnv").decodeValue[AppEnvironment]
+
+source.read("TestingEnv").decodeValue[AppEnvironment]
+
+source.read("invalidEnv").decodeValue[AppEnvironment]
+```
+
+[enumeratum]: https://github.com/lloydmeta/enumeratum
diff --git a/docs/src/main/tut/docs/environments.md b/docs/src/main/tut/docs/environments.md
new file mode 100644
index 00000000..2482e89d
--- /dev/null
+++ b/docs/src/main/tut/docs/environments.md
@@ -0,0 +1,164 @@
+---
+layout: docs
+title: "Multiple Environments"
+permalink: /docs/environments
+---
+
+# Multiple Environments
+Being able to have different configuration values in different environments is one of the main reasons why we use configurations. For example, we might want to run our application in a local, testing, and production environment. To be able to work with multiple environments, we first need a representation of them in our application.
+
+One of the most convenient ways to deal with multiple environments with Ciris, is to define them as an [enumeratum](/docs/enumeratum-module) enumeration. Since Ciris integrates with enumeratum, we get the ability to load values of that enumeration without having to write any additional code. Alternatively, we could define our own representation of multiple environments, and also define a [configuration decoder](/docs/decoders) for that representation.
+
+```tut:silent
+object environments {
+ import enumeratum._
+
+ sealed abstract class AppEnvironment extends EnumEntry
+
+ object AppEnvironment extends Enum[AppEnvironment] {
+ case object Local extends AppEnvironment
+ case object Testing extends AppEnvironment
+ case object Production extends AppEnvironment
+
+ val values = findValues
+ }
+}
+
+import environments._
+import AppEnvironment._
+```
+
+We can now easily load values of that enumeration by just referring to the `AppEnvironment` type. Since the environments are represented as `case object`s, we can, for example, use pattern matching to use different values for different environments as necessary. Values which are the same across environments can remain as they would have before.
+
+First, let's define the configuration we want our application to use. We're using refinement types, together with [refined](/docs/refined-module), to represent our validation logic. For more information, refer to the [encoding validation](/docs/validation) section. The [`Secret`][Secret] type is used to denote secret configuration values, to avoid accidentally including the secrets in logs. The [logging configurations](/docs/logging) section contains more information.
+
+```tut:silent
+import ciris.Secret
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.string.MatchesRegex
+import eu.timepit.refined.types.net.UserPortNumber
+import eu.timepit.refined.types.numeric.PosInt
+import eu.timepit.refined.types.string.NonEmptyString
+import eu.timepit.refined.W
+
+type ApiKey = String Refined MatchesRegex[W.`"[a-zA-Z0-9]{25,40}"`.T]
+
+final case class ApiConfig(
+ key: Secret[ApiKey],
+ port: UserPortNumber,
+ timeoutSeconds: PosInt
+)
+
+final case class Config(
+ appName: NonEmptyString,
+ api: ApiConfig
+)
+```
+
+As part of the configuration loading, we're loading the `AppEnvironment` we should be running in from the `APP_ENV` environment variable. As an example, we're using different default port numbers depending on the environment -- port 4000 in the local and testing environments, and port 9000 in the production environment. Note that the default port is only used if the `http.port` system property has not been set.
+
+```tut:book
+import ciris.{env, loadConfig, prop}
+import ciris.enumeratum._
+import ciris.refined._
+import eu.timepit.refined.auto._
+
+val config =
+ loadConfig(
+ env[AppEnvironment]("APP_ENV"),
+ env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")),
+ prop[Option[UserPortNumber]]("http.port")
+ ) { (environment, apiKey, port) =>
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse (environment match {
+ case Local | Testing => 4000
+ case Production => 9000
+ })
+ )
+ )
+ }
+```
+
+Sometimes it might be necessary to have different configuration loading take place in different environments. Ciris provides the [`withValues`][withValues] (and [`withValue`][withValue] for a single value) function, which allows you to do exactly that. The function takes a number of configuration values, and effectively wraps your [`loadConfig`][loadConfig] functions. For example, let's assume you would want a static configuration in the local and testing environments, while using the configuration loading above in the production environment.
+
+```tut:book
+import ciris.withValue
+
+val config =
+ withValue(env[AppEnvironment]("APP_ENV")) {
+ case Local | Testing =>
+ loadConfig {
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = Secret("RacrqvWjuu4KVmnTG9b6xyZMTP7jnX"),
+ timeoutSeconds = 10,
+ port = 4000
+ )
+ )
+ }
+
+ case Production =>
+ loadConfig(
+ env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")),
+ prop[Option[UserPortNumber]]("http.port")
+ ) { (apiKey, port) =>
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse 4000
+ )
+ )
+ }
+ }
+```
+
+Note that when using [`withValues`][withValues], errors for the specified values (in this case, the `APP_ENV` environment variable) means we will not continue to try to load the configuration, meaning potential further errors are not included. Assuming we could load an `AppEnvironment`, any further errors will be accumulated as you would normally expect.
+
+You might have noticed that there is some duplication of values when loading configurations across the different environments. If we would like to avoid that duplication, we can simply extract the shared parts to a function, like in the following example. Since your configuration models and most values are in code, you can refactor them just like any other code.
+
+```tut:book
+def configWithDefaults(
+ apiKey: Secret[ApiKey],
+ port: Option[UserPortNumber] = None
+): Config = {
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse 4000
+ )
+ )
+}
+
+val config =
+ withValue(env[AppEnvironment]("APP_ENV")) {
+ case Local | Testing =>
+ loadConfig {
+ configWithDefaults(
+ apiKey = Secret("RacrqvWjuu4KVmnTG9b6xyZMTP7jnX")
+ )
+ }
+
+ case Production =>
+ loadConfig(
+ env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")),
+ prop[Option[UserPortNumber]]("http.port")
+ )(configWithDefaults)
+ }
+```
+
+[Secret]: /api/ciris/Secret.html
+[loadConfig]: /api/ciris/index.html#loadConfig[F[_],A1,A2,Z](a1:ciris.ConfigValue[F,A1],a2:ciris.ConfigValue[F,A2])(f:(A1,A2)=>Z)(implicitevidence$5:ciris.api.Functor[F]):F[Either[ciris.ConfigErrors,Z]]
+[withValues]: /api/ciris/index.html#withValues[F[_],A1,A2,Z](a1:ciris.ConfigValue[F,A1],a2:ciris.ConfigValue[F,A2])(f:(A1,A2)=>F[Either[ciris.ConfigErrors,Z]])(implicitevidence$6:ciris.api.Monad[F]):F[Either[ciris.ConfigErrors,Z]]
+[withValue]: /api/ciris/index.html#withValue[F[_],A1,Z](a1:ciris.ConfigValue[F,A1])(f:A1=>F[Either[ciris.ConfigErrors,Z]])(implicitevidence$3:ciris.api.Monad[F]):F[Either[ciris.ConfigErrors,Z]]
diff --git a/docs/src/main/tut/docs/environments/readme.md b/docs/src/main/tut/docs/environments/readme.md
deleted file mode 100644
index 0d3e9c8f..00000000
--- a/docs/src/main/tut/docs/environments/readme.md
+++ /dev/null
@@ -1,98 +0,0 @@
----
-layout: docs
-title: "Multiple Environments"
-position: 5
-permalink: /docs/environments
----
-
-# Multiple Environments
-One of the most common use cases for configurations is having different values for different environments. There are several ways to deal with environments using Ciris: one way being to define an enumeration with [enumeratum][enumeratum] and use the `ciris-enumeratum` module to be able to load values of that enumeration. You can then switch configuration by just writing conditional statements in plain code.
-
-Let's start off by defining an enumeration for our environments: local, testing, and production.
-
-```tut:book
-import enumeratum._
-
-object configuration {
- sealed abstract class AppEnvironment extends EnumEntry
- object AppEnvironment extends Enum[AppEnvironment] {
- case object Local extends AppEnvironment
- case object Testing extends AppEnvironment
- case object Production extends AppEnvironment
-
- val values = findValues
- }
-}
-```
-
-We'll then reuse the same configuration as seen in the [Encoding Validation](/docs/validation) section.
-
-```tut:book
-import eu.timepit.refined.auto._
-import eu.timepit.refined.types.net.PortNumber
-import eu.timepit.refined.types.string.NonEmptyString
-
-import scala.concurrent.duration._
-
-final case class Config(
- apiKey: NonEmptyString,
- timeout: Duration,
- port: PortNumber
-)
-```
-
-Then, let's read the API key and port from the environment, along with which environment the application is running in. In this case, we're using a default configuration, overriding the timeout value in the testing and production environments. If no environment was set, we will use the default configuration.
-
-```tut:book
-import configuration._
-import ciris.enumeratum._
-import ciris.refined._
-import ciris._
-
-val config =
- loadConfig(
- env[NonEmptyString]("API_KEY"),
- prop[Option[PortNumber]]("http.port"),
- env[Option[AppEnvironment]]("APP_ENV")
- ) { (apiKey, port, appEnvironment) =>
- val default =
- Config(
- apiKey = apiKey,
- timeout = 10 seconds,
- port = port getOrElse 4000
- )
-
- appEnvironment match {
- case Some(AppEnvironment.Local) | None => default
- case _ => default.copy(timeout = 5 seconds)
- }
- }
-```
-
-What about reading different configuration values depending on the environment? For example, you could use defaults for everything in a local environment, while reading configuration values, like the API key and port, in the other environments. For that purpose, there is a `withValues` (and `withValue`) method you can use. It works exactly like `loadConfig` except it wraps your `loadConfig` statements, only executing them if all `withValues` values could be read successfully. If it helps, think of `loadConfig` as `map` and `withValues` as `flatMap` (which is also how they are defined internally).
-
-```tut:book
-withValue(env[Option[AppEnvironment]]("APP_ENV")) {
- case Some(AppEnvironment.Local) | None =>
- loadConfig {
- Config(
- apiKey = "changeme",
- timeout = 10 seconds,
- port = 4000
- )
- }
- case _ =>
- loadConfig(
- env[NonEmptyString]("API_KEY"),
- prop[PortNumber]("http.port")
- ) { (apiKey, port) =>
- Config(
- apiKey = apiKey,
- timeout = 5 seconds,
- port = port
- )
- }
-}
-```
-
-[enumeratum]: https://github.com/lloydmeta/enumeratum
diff --git a/docs/src/main/tut/docs/generic-module.md b/docs/src/main/tut/docs/generic-module.md
new file mode 100644
index 00000000..5adc5284
--- /dev/null
+++ b/docs/src/main/tut/docs/generic-module.md
@@ -0,0 +1,103 @@
+---
+layout: docs
+title: "Generic Module"
+permalink: /docs/generic-module
+---
+
+# Generic Module
+The `ciris-generic` module provides the ability to decode products and coproducts using [shapeless][shapeless]. This allows you to decode case classes, [value classes](http://docs.scala-lang.org/overviews/core/value-classes.html), and shapeless coproducts, plus anything else that shapeless `Generic` supports. Let's take a look at these cases to see how it works. We start by defining a source from which we can read configuration values.
+
+```tut:silent
+import ciris.{ConfigKeyType, ConfigSource}
+import ciris.generic._
+
+val source = {
+ val keyType = ConfigKeyType[String]("generic key")
+ ConfigSource.fromEntries(keyType)("key" -> "5.0")
+}
+```
+
+We can then define and load a unary product, for example a case class with one value.
+
+```tut:book
+final case class DoubleValue(value: Double)
+
+source.read("key").decodeValue[DoubleValue]
+```
+
+It also works for value classes and any other unary products shapeless `Generic` supports.
+
+```tut:book
+final case class FloatValue(val value: Float) extends AnyVal
+
+source.read("key").decodeValue[FloatValue]
+```
+
+If we define a product with more than one value:
+
+```tut:book
+final case class TwoValues(value1: Double, value2: Float)
+```
+
+we will try to decode it twice, once as a `Double`, and once as a `Float`.
+
+```tut:book
+source.read("key").decodeValue[TwoValues]
+```
+
+You can also customize the decoding on a per-type basis. For example, if you have a [`ConfigSource`][ConfigSource] which reads values of type `Map[String, String]`, you can customize which key gets decoded for which type, as in the example below.
+
+```tut:book
+import ciris.ConfigDecoder
+
+val mapSource = {
+ val keyType = ConfigKeyType[String]("generic key")
+ ConfigSource.fromEntries(keyType)(
+ "key" -> Map(
+ "key1" -> "1.0",
+ "key2" -> "2.0"
+ )
+ )
+}
+
+implicit val decodeDouble: ConfigDecoder[Map[String, String], Double] =
+ ConfigDecoder.catchNonFatal("Double")(map => map("key1").toDouble)
+
+implicit val decodeFloat: ConfigDecoder[Map[String, String], Float] =
+ ConfigDecoder.catchNonFatal("Float")(map => map("key2").toFloat)
+
+mapSource.read("key").decodeValue[TwoValues]
+```
+
+We can also define a shapeless coproduct and attempt to decode it.
+
+```tut:book
+import shapeless.{:+:, CNil}
+
+type DoubleOrFloat = DoubleValue :+: FloatValue :+: CNil
+
+source.read("key").decodeValue[DoubleOrFloat]
+```
+
+If there is no public constructor or `apply` method:
+
+```tut:book
+object PrivateValues {
+ final class PrivateFloatValue private(val value: Float) extends AnyVal
+ object PrivateFloatValue {
+ def withValue(value: Float) =
+ new PrivateFloatValue(value)
+ }
+}
+
+import PrivateValues._
+```
+
+we will not be able to decode values, resulting in an error at compile-time.
+
+```tut:fail:book
+source.read("key").decodeValue[PrivateFloatValue]
+```
+
+[shapeless]: https://github.com/milessabin/shapeless
+[ConfigSource]: /api/ciris/ConfigSource.html
diff --git a/docs/src/main/tut/docs/logging.md b/docs/src/main/tut/docs/logging.md
new file mode 100644
index 00000000..957d3ee3
--- /dev/null
+++ b/docs/src/main/tut/docs/logging.md
@@ -0,0 +1,77 @@
+---
+layout: docs
+title: "Logging Configurations"
+permalink: /docs/logging
+---
+
+# Logging Configurations
+Configuration logging can quickly help you determine which values are being used by the application, and can aid with debugging whenever things go wrong. Since configurations often contain secret values -- avoiding having secrets in code being one of the main use cases for configurations -- we would like to avoid having these secrets included in any logs. Ciris provides the [`Secret`][Secret] wrapper type for this purpose. By wrapping your secret configuration values in [`Secret`][Secret], we can avoid accidentally including them in logs.
+
+For example, let's take a look at the following configuration, where the `ApiKey` is secret. We're using refinement types to encode validation in the types of our values, refer to the [encoding validation](/docs/validation) section for more information. Note that the `ApiKey` in the example below would normally be loaded from, for example, a vault service -- unless the value itself is not considered a secret, for example, if it was used for testing purposes.
+
+```tut:silent
+import ciris.Secret
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.auto._
+import eu.timepit.refined.string.MatchesRegex
+import eu.timepit.refined.types.net.UserPortNumber
+import eu.timepit.refined.types.numeric.PosInt
+import eu.timepit.refined.types.string.NonEmptyString
+import eu.timepit.refined.W
+
+type ApiKey = String Refined MatchesRegex[W.`"[a-zA-Z0-9]{25,40}"`.T]
+
+final case class ApiConfig(
+ key: Secret[ApiKey],
+ port: UserPortNumber,
+ timeoutSeconds: PosInt
+)
+
+final case class Config(
+ appName: NonEmptyString,
+ api: ApiConfig
+)
+
+val config =
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = Secret("RacrqvWjuu4KVmnTG9b6xyZMTP7jnX"),
+ timeoutSeconds = 10,
+ port = 4000
+ )
+ )
+```
+
+The perhaps easiest way to log the configuration is to use `println`. As you can see in the example below, the secret configuration value is replaced with a `Secret(***)` placeholder when printed, while the remaining values of the configuration are printed with their `toString` representations as expected.
+
+```tut:book
+println(config)
+```
+
+## Logging Improvements
+Relying on `toString` works reasonably well for small configurations, like in the example shown above, but as your configuration grows in size, it can be considerably more difficult to determine which value is which in the output. Making use of `toString` also means we're relying on every type having implemented an appropriate `toString` function, which might not always be the case.
+
+As an alternative, we can make use of the [`Show`][Show] type class from [cats](/docs/cats-module). However, we would still like to avoid having to manually define the [`Show`][Show] behaviour for all the types in the configuration. Luckily, we can use [kittens][kittens], which in turn uses [shapeless][shapeless] to provide generic derivation of type class instances for [`Show`][Show]. The [`Show`][Show] instances derived by [kittens][kittens] also include the field names of our `case class`es, so it's easier to see which configuration value is which in the output. If we're using refinement types, there's a `refined-cats` module which provides [`Show`][Show] instances for refinement types.
+
+```tut:silent
+import cats.Show
+import cats.derived._
+import cats.implicits._
+import ciris.cats._
+import eu.timepit.refined.cats._
+
+implicit val showConfig: Show[Config] = {
+ import auto.show._
+ semi.show
+}
+```
+
+```tut:book
+println(config.show)
+```
+
+[Secret]: /api/ciris/Secret.html
+[kittens]: https://github.com/milessabin/kittens
+[Show]: https://typelevel.org/cats/typeclasses/show.html
+[shapeless]: https://github.com/milessabin/shapeless
diff --git a/docs/src/main/tut/docs/modules.md b/docs/src/main/tut/docs/modules.md
new file mode 100644
index 00000000..09b8e939
--- /dev/null
+++ b/docs/src/main/tut/docs/modules.md
@@ -0,0 +1,120 @@
+---
+layout: docs
+title: "Modules Overview"
+position: 5
+permalink: /docs/modules
+---
+
+```tut:invisible
+val tempFile = {
+ val file = java.io.File.createTempFile("temp-", ".tmp")
+ file.deleteOnExit()
+
+ val writer = new java.io.FileWriter(file)
+ try {
+ writer.write("1234 ")
+ } finally {
+ writer.close()
+ }
+
+ file
+}
+```
+
+# Modules Overview
+The following modules integrate with external libraries to provide extended functionality.
+You might also be interested in the third-party [external libraries](/#external-libraries) which integrate with Ciris.
+
+* The [cats module](/docs/cats-module) enables use of [cats][cats] type class instances.
+
+```tut:silent
+import cats.implicits._
+import ciris.cats._
+import ciris.envF
+import scala.concurrent._
+
+implicit val executionContext: ExecutionContext =
+ ExecutionContext.Implicits.global
+
+envF[Future, Boolean]("CI")
+```
+
+* The [cats-effect module](/docs/cats-effect-module) enables use of [cats-effect][cats-effect] effect types and type class instances.
+
+```tut:silent
+import cats.effect.IO
+import ciris.cats.effect._
+import ciris.fileSync
+
+fileSync[IO, Int](tempFile, _.trim)
+```
+
+* The [enumeratum module](/docs/enumeratum-module) enables decoding of [enumeratum][enumeratum] enumerations.
+
+```tut:silent
+object environments {
+ import enumeratum._
+
+ sealed abstract class AppEnvironment extends EnumEntry
+
+ object AppEnvironment extends Enum[AppEnvironment] {
+ case object Local extends AppEnvironment
+ case object Testing extends AppEnvironment
+ case object Production extends AppEnvironment
+
+ val values = findValues
+ }
+}
+
+import ciris.enumeratum._
+import ciris.env
+import environments._
+
+env[AppEnvironment]("APP_ENV")
+```
+
+* The [generic module](/docs/generic-module) enables generic decoding of product and coproduct types using [shapeless][shapeless].
+
+```tut:silent
+import ciris.generic._
+import ciris.prop
+
+final case class Decibel(val value: Int) extends AnyVal
+
+prop[Decibel]("ratio.delta")
+```
+
+* The [refined module](/docs/refined-module) enables decoding of [refined][refined] refinement types.
+
+```tut:silent
+import ciris.refined._
+import eu.timepit.refined.types.net.UserPortNumber
+
+env[Option[UserPortNumber]]("HTTP_PORT")
+```
+
+* The [spire module](/docs/spire-module) enables decoding of [spire][spire] number types.
+
+```tut:silent
+import ciris.spire._
+import spire.math.Natural
+
+env[Natural]("DELAY_SECONDS")
+```
+
+* The [squants module](/docs/squants-module) enables decoding of [squants][squants] quantities with unit of measure.
+
+```tut:silent
+import ciris.squants._
+import squants.time.Time
+
+prop[Time]("refresh.interval")
+```
+
+[cats]: https://github.com/typelevel/cats
+[cats-effect]: https://github.com/typelevel/cats-effect
+[enumeratum]: https://github.com/lloydmeta/enumeratum
+[shapeless]: https://github.com/milessabin/shapeless
+[refined]: https://github.com/fthomas/refined
+[spire]: https://github.com/non/spire
+[squants]: https://github.com/typelevel/squants
diff --git a/docs/src/main/tut/docs/modules/readme.md b/docs/src/main/tut/docs/modules/readme.md
deleted file mode 100644
index 535b3892..00000000
--- a/docs/src/main/tut/docs/modules/readme.md
+++ /dev/null
@@ -1,285 +0,0 @@
----
-layout: docs
-title: "Modules Overview"
-position: 3
-permalink: /docs/modules
----
-
-# Modules Overview
-Ciris core module, `ciris-core`, provides basic functionality and decoders for many standard library types. See [Core Concepts](/docs/concepts) for an explanation of the concepts in the core module. Remaining sections mostly cover the core module in greater detail, in particular [Configuration Sources](/docs/sources) and [Custom Decoders](/docs/decoders). In the sections below, Ciris' other modules are explained briefly.
-
-## Cats
-The `ciris-cats` module provides typeclass instances for contexts `F[_]` when used together with Ciris. `Id` is the default context, and is used when no explicit context is desired. The `ciris-cats` module is useful in cases when `F` is not `Id`, but for example `Future`, like in the following example.
-
-```tut:book:reset
-import cats.implicits._
-import ciris._
-import ciris.cats._
-import ciris.ConfigError.right
-import scala.concurrent._
-import scala.concurrent.duration._
-
-implicit val executionContext: ExecutionContext =
- ExecutionContext.Implicits.global
-
-val source: ConfigSource[Future, String, String] = {
- val keyType = ConfigKeyType[String]("example key")
- ConfigSource.applyF(keyType) { key: String =>
- Future.successful(right(key.length.toString))
- }
-}
-
-final case class Config(value1: Int, value2: Int)
-
-val futureConfig =
- loadConfig(
- source.read("key1").decodeValue[Int],
- source.read("key2").decodeValue[Int]
- )(Config)
-
-val config = Await.result(futureConfig, 1.second)
-```
-
-## Enumeratum
-The `ciris-enumeratum` module provides support for reading [enumeratum][enumeratum] enumerations. You can refer to the [documentation](/api/ciris/enumeratum) for a complete list of supported enumerations. As an example, let's define an `Enum` and see how we can load values of that enumeration using Ciris. Enumeratum provides mixins, like `Lowercase` below, which we can use to customize the name of our enumerations, and in turn, what values we will be able to load with Ciris.
-
-```tut:book:reset
-import enumeratum._
-import enumeratum.EnumEntry._
-
-object configuration {
- sealed abstract class AppEnvironment extends EnumEntry with Lowercase
- object AppEnvironment extends Enum[AppEnvironment] {
- case object Local extends AppEnvironment
- case object Testing extends AppEnvironment
- case object Production extends AppEnvironment
-
- val values = findValues
- }
-}
-```
-
-Let's also define a custom [configuration source](/docs/sources), holding some values we can ask Ciris to load.
-
-```tut:book
-import ciris._
-import ciris.enumeratum._
-import configuration._
-
-val source = {
- val keyType = ConfigKeyType[String]("enumeratum key")
- ConfigSource.fromMap(keyType)(Map(
- "localEnv" -> "local",
- "testingEnv" -> "testing",
- "TestingEnv" -> "Testing",
- "invalidEnv" -> "invalid"
- ))
-}
-```
-
-We can then ask Ciris to load values from the source and decode them as enumeration values.
-
-```tut:book
-source.read("localEnv").decodeValue[AppEnvironment]
-
-source.read("testingEnv").decodeValue[AppEnvironment]
-
-source.read("TestingEnv").decodeValue[AppEnvironment]
-
-source.read("invalidEnv").decodeValue[AppEnvironment]
-```
-
-Dealing with multiple environments is a common use case for configurations, explained in greater detail in the [Multiple Environments](/docs/environments) section.
-
-## Generic
-The `ciris-generic` module provides the ability to decode products and coproducts using [shapeless][shapeless]. This allows you to decode case classes, [value classes](http://docs.scala-lang.org/overviews/core/value-classes.html), and shapeless coproducts, plus anything else that shapeless' `Generic` supports. Let's take a brief look at these cases to see how it works in practice. We start by defining a source from which we can read configuration values.
-
-```tut:book:reset
-import ciris._
-import ciris.generic._
-
-val source = {
- val keyType = ConfigKeyType[String]("generic key")
- ConfigSource.fromMap(keyType)(Map("key" -> "5.0"))
-}
-```
-
-We can then define and load a unary product, for example a case class with one value.
-
-```tut:book
-final case class DoubleValue(value: Double)
-
-source.read("key").decodeValue[DoubleValue]
-```
-
-It also works for value classes and any other unary products shapeless' `Generic` supports.
-
-```tut:book
-final class FloatValue(val value: Float) extends AnyVal
-
-source.read("key").decodeValue[FloatValue]
-```
-
-We can also define a shapeless coproduct and load it.
-
-```tut:book
-import shapeless.{:+:, CNil}
-
-type DoubleOrFloat = DoubleValue :+: FloatValue :+: CNil
-
-source.read("key").decodeValue[DoubleOrFloat]
-```
-
-If we define a product with more than one value:
-
-```tut:book
-final case class TwoValues(value1: Double, value2: Float)
-```
-
-we will try to decode it twice, once as a `Double`, and once as a `Float`.
-
-```tut:book
-source.read("key").decodeValue[TwoValues]
-```
-
-You also customize the decoding on a per-type basis. For example, if you have a `ConfigSource` which reads `Map[String, String]` values, you can customize which key gets decoded for which type, like in the following example.
-
-```tut:book
-val mapSource = {
- val keyType = ConfigKeyType[String]("generic key")
- ConfigSource.fromMap(keyType)(Map("key" -> Map("key1" -> "1.0", "key2" -> "2.0")))
-}
-
-implicit val decodeDouble: ConfigDecoder[Map[String, String], Double] =
- ConfigDecoder.catchNonFatal("Double")(map => map("key1").toDouble)
-
-implicit val decodeFloat: ConfigDecoder[Map[String, String], Float] =
- ConfigDecoder.catchNonFatal("Float")(map => map("key2").toFloat)
-
-mapSource.read("key").decodeValue[TwoValues]
-```
-
-If there is no public constructor or apply method:
-
-```tut:book
-object PrivateValues {
- final class PrivateFloatValue private(val value: Float) extends AnyVal
- object PrivateFloatValue {
- def withValue(value: Float) =
- new PrivateFloatValue(value)
- }
-}
-
-import PrivateValues._
-```
-
-we will not be able to decode a value, resulting in an error at compile-time.
-
-```tut:fail:book
-source.read("key").decodeValue[PrivateFloatValue]
-```
-
-## Refined
-The `ciris-refined` module allows you to load refinement types from [refined][refined]. Let's start by defining a configuration source from which to read some values.
-
-```tut:book:reset
-import ciris._
-import ciris.refined._
-
-val source = {
- val keyType = ConfigKeyType[String]("refined key")
- ConfigSource.fromMap(keyType)(Map(
- "negative" -> "-1",
- "zero" -> "0",
- "positive" -> "1",
- "other" -> "abc"
- ))
-}
-```
-
-In this example, we'll simply try to read `PosInt` values, which are all `Int`s strictly greater than zero.
-
-```tut:book
-import eu.timepit.refined.types.numeric.PosInt
-
-source.read("negative").decodeValue[PosInt]
-
-source.read("zero").decodeValue[PosInt]
-
-source.read("positive").decodeValue[PosInt]
-
-source.read("other").decodeValue[PosInt]
-```
-
-Refinement types are useful for making sure your configuration is valid. See the [Encoding Validation](/docs/validation) section for more information.
-
-## Spire
-The `ciris-spire` module adds support for loading [spire][spire] number types. For a complete list of support number types, refer to the [documentation](/api/ciris/spire). Let's see how we can load spire number types by first defining a custom configuration source.
-
-```tut:book:reset
-import spire.math._
-import ciris._
-import ciris.spire._
-
-val source = {
- val keyType = ConfigKeyType[String]("spire key")
- ConfigSource.fromEntries(keyType)(
- "natural" -> "847365894625891365137596378546725",
- "interval" -> "(1/3, 524/51]",
- "rational" -> "-194712/-129831",
- "uint" -> (Int.MaxValue.toLong + 1L).toString
- )
-}
-```
-
-We can then simply read entries from the source, using `read`, and decode the values into spire number types.
-
-```tut:book
-source.read("natural").decodeValue[Natural]
-
-source.read("interval").decodeValue[Interval[Rational]]
-
-source.read("rational").decodeValue[Rational]
-
-source.read("natural").decodeValue[Number]
-
-source.read("rational").decodeValue[Real]
-
-source.read("trilean").decodeValue[Trilean]
-
-source.read("uint").decodeValue[UInt]
-```
-
-## Squants
-The `ciris-squants` module allows loading values with unit of measure from [squants][squants]. For a complete list of supported dimensions, refer to the [documentation](/api/ciris/squants). Let's see how this works by first defining a configuration source with a few different keys mapping to `Time` values, each having a different unit of measure.
-
-```tut:book:reset
-import squants.time.Time
-import ciris._
-import ciris.squants._
-
-val source = {
- val keyType = ConfigKeyType[String]("squants key")
- ConfigSource.fromMap(keyType)(Map(
- "seconds" -> "3 s",
- "minutes" -> "23 m",
- "hours" -> "12 h"
- ))
-}
-```
-
-We can then load these entries and decode the values into type `Time`.
-
-```tut:book
-val seconds = source.read("seconds").decodeValue[Time]
-val minutes = source.read("minutes").decodeValue[Time]
-val hours = source.read("hours").decodeValue[Time]
-
-loadConfig(seconds, minutes, hours)(_ + _ + _).right.map(_.toMinutes)
-```
-
-[enumeratum]: https://github.com/lloydmeta/enumeratum
-[refined]: https://github.com/fthomas/refined
-[shapeless]: https://github.com/milessabin/shapeless
-[spire]: https://github.com/non/spire
-[squants]: https://github.com/typelevel/squants
diff --git a/docs/src/main/tut/docs/refined-module.md b/docs/src/main/tut/docs/refined-module.md
new file mode 100644
index 00000000..49055c11
--- /dev/null
+++ b/docs/src/main/tut/docs/refined-module.md
@@ -0,0 +1,39 @@
+---
+layout: docs
+title: "Refined Module"
+permalink: /docs/refined-module
+---
+
+# Refined Module
+The `ciris-refined` module enables decoding of [refined][refined] refinement types. This is especially useful for [encoding validation](/docs/validation) in the types of your configuration values. Let's see how we can decode refinement types. We'll start by defining a [configuration source](/docs/sources) from which to read some configuration values.
+
+```tut:silent
+import ciris.{ConfigKeyType, ConfigSource}
+import ciris.refined._
+
+val source = {
+ val keyType = ConfigKeyType[String]("refined key")
+ ConfigSource.fromEntries(keyType)(
+ "negative" -> "-1",
+ "zero" -> "0",
+ "positive" -> "1",
+ "other" -> "abc"
+ )
+}
+```
+
+In this example, we'll simply try to read `PosInt` values, which are all `Int`s strictly greater than zero.
+
+```tut:book
+import eu.timepit.refined.types.numeric.PosInt
+
+source.read("negative").decodeValue[PosInt]
+
+source.read("zero").decodeValue[PosInt]
+
+source.read("positive").decodeValue[PosInt]
+
+source.read("other").decodeValue[PosInt]
+```
+
+[refined]: https://github.com/fthomas/refined
diff --git a/docs/src/main/tut/docs/sources.md b/docs/src/main/tut/docs/sources.md
new file mode 100644
index 00000000..5250d9ce
--- /dev/null
+++ b/docs/src/main/tut/docs/sources.md
@@ -0,0 +1,127 @@
+---
+layout: docs
+title: "Configuration Sources"
+position: 3
+permalink: /docs/sources
+---
+
+# Configuration Sources
+Configuration sources are represented in Ciris with [`ConfigSource`][ConfigSource]s, and are essentially functions which can retrieve values of type `V` in context `F`, for keys of type `K`, and which handle errors. More specifically, [`ConfigSource`][ConfigSource]s require a [`ConfigKeyType`][ConfigKeyType], which is a description of the type of keys the source supports. [`ConfigSource`][ConfigSource]s can then be expressed as `K => ConfigEntry[F, K, V, V]`, and a simplified definition of [`ConfigSource`][ConfigSource] is provided below.
+
+```tut:silent
+import ciris.{ConfigEntry, ConfigKeyType}
+
+{
+ abstract class ConfigSource[F[_], K, V](val keyType: ConfigKeyType[K]) {
+ def read(key: K): ConfigEntry[F, K, V, V]
+ }
+}
+```
+
+[`ConfigKeyType`][ConfigKeyType] includes the name and type of the key. Ciris includes [`ConfigKeyType`][ConfigKeyType]s for keys used by sources supported in the core module, which include the following. You can easily create your own [`ConfigKeyType`][ConfigKeyType]s by specifying the name and type of the key.
+
+```tut:book
+ConfigKeyType.Argument
+
+ConfigKeyType.Environment
+
+ConfigKeyType.File
+
+ConfigKeyType.Property
+
+ConfigKeyType[String]("custom key")
+```
+
+[`ConfigEntry`][ConfigEntry]s include the key which was retrieved, the [`ConfigKeyType`][ConfigKeyType], the unmodified source value retrieved from the [`ConfigSource`][ConfigSource], and the source value with additional transformations applied -- for example, the result of attempting to decode the source value to type `Int`. A simplified definition of [`ConfigEntry`][ConfigEntry] is provided below.
+
+```tut:silent
+import ciris.{ConfigError, ConfigValue}
+import ciris.api.Apply
+
+{
+ final class ConfigEntry[F[_]: Apply, K, S, V] private (
+ val key: K,
+ val keyType: ConfigKeyType[K],
+ val sourceValue: F[Either[ConfigError, S]],
+ val value: F[Either[ConfigError, V]]
+ ) extends ConfigValue[F, V]
+}
+```
+
+Both the source value and the value might not be available, and in such cases, the [`ConfigError`][ConfigError] will provide more details. [`ConfigEntry`][ConfigEntry]s require that the context `F` has an [`Apply`][Apply] instance defined, to be able to transform the value, and to combine multiple values. Ciris provides convenience functions, like [`env`][env], [`prop`][prop], and [`file`][file] (and [`envF`][envF], [`propF`][propF], and [`fileSync`][fileSync] for suspending effects), which all make use of [`ConfigSource`][ConfigSource]s, and return [`ConfigEntry`][ConfigEntry]s. These convenience functions also attempts to decode the value with a [configuration decoder](/docs/decoders), represented with [`ConfigDecoder`][ConfigDecoder]s. For example, here is how you could define `env` for reading environment variables.
+
+```tut:silent
+import ciris.{ConfigDecoder, ConfigSource}
+import ciris.api.Id
+
+{
+ def env[Value](key: String)(
+ implicit decoder: ConfigDecoder[String, Value]
+ ): ConfigEntry[Id, String, String, Value] = {
+ ConfigSource.Environment
+ .read(key)
+ .decodeValue[Value]
+ }
+}
+```
+
+Ciris provides many convenience functions for creating [`ConfigSource`][ConfigSource]s in the [companion object][ConfigSourceCompanion] of [`ConfigSource`][ConfigSource]. For more information on how to create additional [`ConfigSource`][ConfigSource]s, please refer to the [supporting new sources](/docs/supporting-new-sources) section.
+
+## Source Transformations
+Configuration sources can be transformed in different ways. Most notably, we can take an existing [`ConfigSource`][ConfigSource] and suspend the reading of values into context `G`, by using [`suspendF`][suspendF], provided that there is a [`Sync`][Sync] instance defined for `G`, and a natural transformation `F ~> G`. This effectively means that we can take a synchronous _impure_ configuration source, and make it _pure_ with [`suspendF`][suspendF].
+
+For example, system properties are mutable, and reading them is not _pure_ by definition -- since we can get different results for the same arguments, if the properties are modified in-between the function invocations. We can create a _pure_ version of [`ConfigSource.Properties`][ConfigSourceProperties] by making use of [`suspendF`][suspendF] and, for example, `IO` from [cats-effect][cats-effect].
+
+```tut:book
+import cats.effect.IO
+import ciris.cats.effect._
+
+ConfigSource.Properties.suspendF[IO]
+```
+
+The natural transformation `Id ~> F` (where `F` is `IO` here) exists as long as we have an `Applicative[F]` defined, which we have via the `Sync[F]` instance. If we take a look at [`propF`][propF], which is the _pure_ version of [`prop`][prop], we'll see that it's also defined in a very similar fashion with [`suspendF`][suspendF].
+
+```tut:silent
+import ciris.api.Sync
+
+{
+ def propF[F[_]: Sync, Value](key: String)(
+ implicit decoder: ConfigDecoder[String, Value]
+ ): ConfigEntry[F, String, String, Value] = {
+ ConfigSource.Properties
+ .suspendF[F]
+ .read(key)
+ .decodeValue[Value]
+ }
+}
+```
+
+If we simply want to transform the context of a [`ConfigSource`][ConfigSource], we can instead use [`transformF`][transformF], which expects a natural transformation `F ~> G`, and that there is an [`Apply`][Apply] instance for `G`. The [`transformF`][transformF] function which is available on [`ConfigSource`][ConfigSource] makes use of the similar [`transformF`][transformFentry] on [`ConfigEntry`][ConfigEntry].
+
+```tut:book
+ConfigSource.Environment.transformF[IO]
+```
+
+Sometimes it's necessary to combine suspended reading and memoization in a context `F`, and the [cats-effect](/docs/cats-effect-module) module provides the [`suspendMemoizeF`][suspendMemoizeF] function on [`ConfigSource`][ConfigSource] for this purpose. The function supports any `F` for which a [`LiftIO`][LiftIO] instance is available. The [supporting new sources](/docs/supporting-new-sources#suspending-effects) section provides an example of how the function can be used.
+
+[cats-effect]: https://github.com/typelevel/cats-effect
+[ConfigSource]: /api/ciris/ConfigSource.html
+[ConfigDecoder]: /api/ciris/ConfigDecoder.html
+[ConfigKeyType]: /api/ciris/ConfigKeyType.html
+[ConfigEntry]: /api/ciris/ConfigEntry.html
+[ConfigError]: /api/ciris/ConfigError.html
+[Apply]: /api/ciris/api/Apply.html
+[Sync]: /api/ciris/api/Sync.html
+[ConfigSourceCompanion]: /api/ciris/ConfigSource$.html
+[transformF]: /api/ciris/ConfigSource.html#transformF[G[_]](implicitevidence$2:ciris.api.Apply[G],implicitf:F~>G):ciris.ConfigSource[G,K,V]
+[transformFentry]: /api/ciris/ConfigEntry.html#transformF[G[_]](implicitevidence$2:ciris.api.Apply[G],implicitf:F~>G):ciris.ConfigEntry[G,K,S,V]
+[ConfigSourceProperties]: /api/ciris/ConfigSource$.html#Properties
+[suspendF]: /api/ciris/ConfigSource.html#suspendF[G[_]](implicitevidence$1:ciris.api.Sync[G],implicitf:F~>G):ciris.ConfigSource[G,K,V]
+[env]: /api/ciris/index.html#env[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[prop]: /api/ciris/index.html#prop[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[file]: /api/ciris/index.html#file[Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,(java.io.File,java.nio.charset.Charset),String,Value]
+[envF]: /api/ciris/index.html#envF[F[_],Value](key:String)(implicitevidence$1:ciris.api.Applicative[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[fileSync]: /api/ciris/index.html#fileSync[F[_],Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitevidence$1:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,(java.io.File,java.nio.charset.Charset),String,Value]
+[propF]: /api/ciris/index.html#propF[F[_],Value](key:String)(implicitevidence$2:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[suspendMemoizeF]: /api/ciris/cats/effect/syntax$$CatsEffectConfigSourceIdSyntax.html#suspendMemoizeF[F[_]](implicitevidence$1:ciris.api.Apply[F],implicitevidence$2:cats.effect.LiftIO[F]):ciris.ConfigSource[F,K,V]
+[LiftIO]: https://github.com/typelevel/cats-effect/blob/master/core/shared/src/main/scala/cats/effect/LiftIO.scala
diff --git a/docs/src/main/tut/docs/sources/readme.md b/docs/src/main/tut/docs/sources/readme.md
deleted file mode 100644
index 169c044f..00000000
--- a/docs/src/main/tut/docs/sources/readme.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-layout: docs
-title: "Configuration Sources"
-position: 6
-permalink: /docs/sources
----
-
-# Configuration Sources
-Ciris provides support for reading environment variables, system properties, command-line arguments, and files. If you're looking for a common configuration source not yet supported, please [file an issue](https://github.com/vlovgr/ciris/issues/new) or, even better, submit a pull-request. If you require other configurations sources, you can easily define your own. You'll find helper methods for creating custom configuration sources in the companion object of [`ConfigSource`](https://cir.is/api/ciris/ConfigSource$.html). Let's illustrate this by writing a configuration source which captures the current system properties at a certain point in time, ignoring any later changes.
-
-To create a configuration source, we need to provide a `ConfigKeyType` which is the name of the type of key the source reads (to support sensible error messages). We can convert the current system properties to a `Map` and create a `ConfigSource` from it using the `fromMap` method.
-
-```tut:invisible
-if(!sys.props.get("file.encoding").isDefined)
- sys.props.put("file.encoding", "UTF-8")
-```
-
-```tut:book
-import ciris._
-
-val source = {
- val keyType = ConfigKeyType[String]("fixed system property")
- ConfigSource.fromMap(keyType)(sys.props.toMap)
-}
-
-source.read("file.encoding")
-
-prop[String]("file.encoding")
-```
-
-To verify that the source does not change, let's delete the `file.encoding` key, and verify that we can still read it.
-
-```tut:book
-sys.props.remove("file.encoding")
-
-source.read("file.encoding")
-
-prop[String]("file.encoding")
-```
diff --git a/docs/src/main/tut/docs/spire-module.md b/docs/src/main/tut/docs/spire-module.md
new file mode 100644
index 00000000..f94dada0
--- /dev/null
+++ b/docs/src/main/tut/docs/spire-module.md
@@ -0,0 +1,44 @@
+---
+layout: docs
+title: "Spire Module"
+permalink: /docs/spire-module
+---
+
+# Spire Module
+The `ciris-spire` module enables decoding of [spire][spire] number types. For a complete list of supported types, refer to the [documentation](/api/ciris/spire). Let's see how we can decode spire number types by first defining a custom [configuration source](/docs/sources) from which to load some configuration values.
+
+```tut:silent
+import ciris.{ConfigKeyType, ConfigSource}
+import ciris.spire._
+import spire.math._
+
+val source = {
+ val keyType = ConfigKeyType[String]("spire key")
+ ConfigSource.fromEntries(keyType)(
+ "natural" -> "847365894625891365137596378546725",
+ "interval" -> "(1/3, 524/51]",
+ "rational" -> "-194712/-129831",
+ "uint" -> (Int.MaxValue.toLong + 1L).toString
+ )
+}
+```
+
+We can then read entries from the source and decode values into spire number types.
+
+```tut:book
+source.read("natural").decodeValue[Natural]
+
+source.read("interval").decodeValue[Interval[Rational]]
+
+source.read("rational").decodeValue[Rational]
+
+source.read("natural").decodeValue[Number]
+
+source.read("rational").decodeValue[Real]
+
+source.read("trilean").decodeValue[Trilean]
+
+source.read("uint").decodeValue[UInt]
+```
+
+[spire]: https://github.com/non/spire
diff --git a/docs/src/main/tut/docs/squants-module.md b/docs/src/main/tut/docs/squants-module.md
new file mode 100644
index 00000000..ff0d30ae
--- /dev/null
+++ b/docs/src/main/tut/docs/squants-module.md
@@ -0,0 +1,35 @@
+---
+layout: docs
+title: "Squants Module"
+permalink: /docs/squants-module
+---
+
+# Squants Module
+The `ciris-squants` module enables decoding of quantities with unit of measure from [squants][squants]. For a complete list of supported dimensions, refer to the [documentation](/api/ciris/squants). Let's see how this works by first defining a [configuration source](/docs/sources) with a few different keys mapping to `Time` values, each having a different unit of measure.
+
+```tut:silent
+import ciris.{ConfigKeyType, ConfigSource}
+import ciris.squants._
+import squants.time.Time
+
+val source = {
+ val keyType = ConfigKeyType[String]("squants key")
+ ConfigSource.fromEntries(keyType)(
+ "seconds" -> "3 s",
+ "minutes" -> "23 m",
+ "hours" -> "12 h"
+ )
+}
+```
+
+We can then read entries from the source and decode values as `Time`.
+
+```tut:book
+val seconds = source.read("seconds").decodeValue[Time]
+
+val minutes = source.read("minutes").decodeValue[Time]
+
+val hours = source.read("hours").decodeValue[Time]
+```
+
+[squants]: https://github.com/typelevel/squants
diff --git a/docs/src/main/tut/docs/supported-sources.md b/docs/src/main/tut/docs/supported-sources.md
new file mode 100644
index 00000000..0bf1fc3f
--- /dev/null
+++ b/docs/src/main/tut/docs/supported-sources.md
@@ -0,0 +1,88 @@
+---
+layout: docs
+title: "Current Supported Sources"
+permalink: /docs/supported-sources
+---
+
+```tut:invisible
+val args: Array[String] = Array("10")
+
+val (tempFile, tempFileName) = {
+ val file = java.io.File.createTempFile("temp-", ".tmp")
+ file.deleteOnExit()
+
+ val writer = new java.io.FileWriter(file)
+ try {
+ writer.write("1234 ")
+ } finally {
+ writer.close()
+ }
+
+ (file, file.getPath.toString)
+}
+```
+
+# Current Supported Sources
+The following sources are the currently supported [configuration sources](/docs/sources) in the core module. The core module only includes configuration sources which can be supported without any external dependencies. [External libraries](/#external-libraries), like [ciris-kubernetes](https://github.com/ovotech/ciris-kubernetes) and [ciris-aws-ssm](https://github.com/ovotech/ciris-aws-ssm), provide support for configuration sources which do not meet this requirement. You can also easily write your own configuration sources, refer to the [supporting new sources](/docs/supporting-new-sources) section for more information.
+
+- _Command-line arguments_ are supported with [`arg`][arg] and [`argF`][argF] (for suspending reading on mutable `IndexedSeq`). If you're writing non-trivial command-line applications, you might want to take a look at dedicated full-featured command-line parsing libraries, like [decline](https://github.com/bkirwi/decline).
+
+```tut:book
+import cats.effect.IO
+import ciris.{arg, argF}
+import ciris.cats.effect._
+
+// The arguments from main(Array[String]): Unit
+args
+
+arg[Int](args)(0)
+
+argF[IO, Int](args)(0)
+```
+
+- _Environment variables_ are supported with the [`env`][env] and [`envF`][envF] (for lifting values into context `F`) functions. Since environment variables are immutable, both the [`env`][env] and [`envF`][envF] functions are pure and safe to use. Note that [`envF`][envF] is just making use of [`transformF`][transformF] on [`ConfigEntry`][ConfigEntry].
+
+```tut:book
+import ciris.{env, envF}
+
+env[String]("LANG")
+
+envF[IO, String]("LANG")
+```
+
+- _Files_ are supported with the [`file`][file] and [`fileWithName`][fileWithName] (and [`fileSync`][fileSync] and [`fileWithNameSync`][fileWithNameSync] for suspending reading) functions, which read the file contents of a specified file, optionally transforming the file contents before trying to convert it to the specified type. Note that reading files with [`file`][file] or [`fileWithName`][fileWithName] is not pure, so using [`fileSync`][fileSync] or [`fileWithNameSync`][fileWithNameSync] is generally recommended.
+
+```tut:book
+import ciris.{file, fileSync, fileWithName, fileWithNameSync}
+
+file[Int](tempFile, _.trim)
+
+fileSync[IO, Int](tempFile, _.trim)
+
+fileWithName[Int](tempFileName, _.trim)
+
+fileWithNameSync[IO, Int](tempFileName, _.trim)
+```
+
+- _System properties_ are supported with the [`prop`][prop] and [`propF`][propF] (for suspending reading) functions. Note that system properties are mutable, so using [`prop`][prop] is generally not pure if system properties are modified at runtime. If you want to be on the safe side, prefer to use [`propF`][propF] for reading system properties.
+
+```tut:book
+import ciris.{prop, propF}
+
+prop[String]("file.encoding")
+
+propF[IO, String]("file.encoding")
+```
+
+[arg]: /api/ciris/index.html#arg[Value](args:IndexedSeq[String])(index:Int)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,Int,String,Value]
+[argF]: /api/ciris/index.html#argF[F[_],Value](args:IndexedSeq[String])(index:Int)(implicitevidence$3:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,Int,String,Value]
+[envF]: /api/ciris/index.html#envF[F[_],Value](key:String)(implicitevidence$1:ciris.api.Applicative[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[fileSync]: /api/ciris/index.html#fileSync[F[_],Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitevidence$1:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,(java.io.File,java.nio.charset.Charset),String,Value]
+[fileWithNameSync]: /api/ciris/index.html#fileWithNameSync[F[_],Value](name:String,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitevidence$2:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,(java.io.File,java.nio.charset.Charset),String,Value]
+[propF]: /api/ciris/index.html#propF[F[_],Value](key:String)(implicitevidence$2:ciris.api.Sync[F],implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[F,String,String,Value]
+[env]: /api/ciris/index.html#env[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[prop]: /api/ciris/index.html#prop[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[file]: /api/ciris/index.html#file[Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,(java.io.File,java.nio.charset.Charset),String,Value]
+[fileWithName]: /api/ciris/index.html#fileWithName[Value](name:String,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,(java.io.File,java.nio.charset.Charset),String,Value]
+[transformF]: /api/ciris/ConfigEntry.html#transformF[G[_]](implicitevidence$2:ciris.api.Apply[G],implicitf:F~>G):ciris.ConfigEntry[G,K,S,V]
+[ConfigEntry]: /api/ciris/ConfigEntry.html
diff --git a/docs/src/main/tut/docs/supported-types.md b/docs/src/main/tut/docs/supported-types.md
new file mode 100644
index 00000000..5c011f55
--- /dev/null
+++ b/docs/src/main/tut/docs/supported-types.md
@@ -0,0 +1,64 @@
+---
+layout: docs
+title: "Current Supported Types"
+permalink: /docs/supported-types
+---
+
+# Current Supported Types
+Ciris has support for many standard library types, and integrates with libraries like [enumeratum](/docs/enumeratum-module), [refined](/docs/refined-module), [spire](/docs/spire-module), and [squants](/docs/squants-module), to support even more types. If you're trying to decode a standard library type, it's likely that it should be supported by Ciris, so please [file an issue](https://github.com/vlovgr/ciris/issues/new) or, even better, submit a pull request. The same applies if you're trying to integrate with a library for which Ciris does not already provide a module.
+
+The following basic types have support for decoding from `String` in the core module.
+
+- `Boolean`, `Byte`, `Char`, `Double`, `Float`, `Int`, `Long`, `Short`, and `String`.
+ - `Double` and `Float` decoders also support percent format ending with `%`.
+ - `Boolean`s can be decoded from `true`/`false`, `yes`/`no`, and `on`/`off`.
+- `java.io`: `File`.
+- `java.math`: `BigInteger` and `BigDecimal`.
+- `java.net`: `InetAddress`, `URI`, and `URL`.
+- `java.nio.charset`: `Charset`.
+- `java.nio.file`: `Path`.
+- `java.time.chrono`: `Chronology`, `HijrahEra`, `IsoEra`, `JapaneseEra`, `MinguoEra`, `ThaiBuddhistEra`.
+- `java.time.format`: `DateTimeFormatter`, `FormatStyle`, `ResolverStyle`, `SignStyle`, and `TextStyle`.
+- `java.time`: `DayOfWeek`, `Duration`, `Instant`, `LocalDate`, `LocalDateTime`, `LocalTime`, `Month`, `MonthDay`, `OffsetDateTime`, `OffsetTime`, `Period`, `Year`, `YearMonth`, `ZonedDateTime`, `ZoneId`, and `ZoneOffset`. Some of these decoders can optionally be configured, see the [configurable decoders](#configurable-decoders) section.
+- `java.util.regex`: `Pattern`.
+- `java.util`: `UUID`.
+- `scala.concurrent.duration`: `Duration` and `FiniteDuration`.
+- `scala.math`: `BigInt` and `BigDecimal`.
+- `scala.util.matching`: `Regex`.
+
+The following composite types are supported in the core module.
+
+- `Option` for values which are not required to be present. See the [usage basics](/docs/basics#configuration-values) section for an example.
+- [`Secret`][Secret] for avoiding logging secret configuration values. See [logging configurations](/docs/logging) for more details.
+
+Modules provide support for additional types, please refer to [modules overview](/docs/modules) for more information.
+For an explanation of how you can write decoders to support new types, refer to [supporting new types](/docs/supporting-new-types).
+
+## Configurable Decoders
+Some `java.time` decoders can optionally be configured to use a different parsing format. Unless configured, decoders will rely on the default parsing behaviour and format. These decoders look for an implicit `DateTimeFormatter`, so to override the behaviour, simply provide one in scope.
+
+```tut:book
+import ciris.{ConfigEntry, ConfigKeyType}
+import java.time.format.DateTimeFormatter
+import java.time.LocalDate
+
+val entry = ConfigEntry("key", ConfigKeyType("key type"), Right("20180307"))
+
+implicit val format: DateTimeFormatter =
+ DateTimeFormatter.ofPattern("yyyyMMdd")
+
+entry.decodeValue[LocalDate]
+```
+
+Note that the implicit `DateTimeFormatter` format will be used for all configurable `java.time` decoders in its scope. If you're decoding multiple `java.time` types, you might need to limit the scope of the implicit format. Alternatively, you can also create the decoder manually by specifying the format to use.
+
+```tut:book
+import ciris.ConfigDecoder
+
+implicit val localDateDecoder: ConfigDecoder[String, LocalDate] =
+ ConfigDecoder.localDateConfigDecoder(format)
+
+entry.decodeValue[LocalDate]
+```
+
+[Secret]: /api/ciris/Secret.html
diff --git a/docs/src/main/tut/docs/supporting-new-sources.md b/docs/src/main/tut/docs/supporting-new-sources.md
new file mode 100644
index 00000000..847b5dd2
--- /dev/null
+++ b/docs/src/main/tut/docs/supporting-new-sources.md
@@ -0,0 +1,244 @@
+---
+layout: docs
+title: "Supporting New Sources"
+permalink: /docs/supporting-new-sources
+---
+
+```tut:invisible
+val (tempFile, tempFileName) = {
+ val file = java.io.File.createTempFile("temp-", ".properties")
+ file.deleteOnExit()
+
+ val props = new java.util.Properties
+ props.put("port", "9000")
+
+ val stream = new java.io.FileOutputStream(file)
+ try {
+ props.store(stream, "")
+ } finally {
+ stream.close()
+ }
+
+ (file, file.getPath.toString)
+}
+```
+
+# Supporting New Sources
+Ciris already has [support](/docs/supported-sources) for common sources in the core module, while [external libraries](/#external-libraries) provide additional [configuration sources](/docs/sources). However, it's also easy to create your own configuration sources, and Ciris provides many helper functions in the [companion object][ConfigSourceCompanion] of [`ConfigSource`][ConfigSource] for that purpose. Following, we'll show how we can create a simple configuration source for reading [property files](https://en.wikipedia.org/wiki/.properties). While property files generally shouldn't be necessary when using _configurations as code_, they can definitely be supported with Ciris when necessary.
+
+We start by defining a [`ConfigSource`][ConfigSource] for reading property files as `Map[String, String]`s. We could reuse the existing [`ConfigSource.File`][ConfigSourceFile] for reading files, but we would rather avoid having to create an intermediate `String` representation, so we'll instead define our own [`ConfigSource`][ConfigSource] for property files.
+
+```tut:silent
+import ciris.{ConfigKeyType, ConfigSource}
+import ciris.api.Id
+import java.io.{File, FileInputStream, InputStreamReader}
+import java.nio.charset.Charset
+import java.util.Properties
+import scala.collection.JavaConverters._
+import scala.util.Try
+
+val propFileSource: ConfigSource[Id, (File, Charset), Map[String, String]] =
+ ConfigSource.catchNonFatal(ConfigKeyType.File) {
+ case (file, charset) =>
+ val fis = new FileInputStream(file)
+ try {
+ val isr = new InputStreamReader(fis, charset)
+ val props = new Properties
+ props.load(isr)
+ props.asScala.toMap
+ } finally {
+ val _ = Try(fis.close())
+ }
+ }
+```
+
+The [`ConfigSource`][ConfigSource] is using the existing [`ConfigKeyType.File`][ConfigKeyTypeFile], which uses `(File, Charset)` as the key type. The source also makes use of [`ConfigSource.catchNonFatal`][ConfigSourceCatchNonFatal] to catch any exceptions when reading the properties file. Finally, the properties are converted to a `Map`, and the `FileInputStream` is closed, ignoring any closing exceptions.
+
+The `PropFileKey` case class fully identifies a property file key. It is a combination of the `File`, `Charset`, and `String` key which we are retrieving. The `toString` function has been overridden to provide the `String` representation we would like in error messages. We'll also describe the name and type of the key by creating a [`ConfigKeyType`][ConfigKeyType].
+
+```tut:silent
+final case class PropFileKey(
+ file: File,
+ charset: Charset,
+ key: String
+) {
+ override def toString: String =
+ s"file = $file, charset = $charset, key = $key"
+}
+
+val propFileKeyType: ConfigKeyType[PropFileKey] =
+ ConfigKeyType("property file key")
+```
+
+Since we would like to avoid having to read the file contents multiple times when reading more than one key, we define a helper class `PropFileAt`, which partially applies `PropFileKey` on `File` and `Charset`, reading the file once for multiple keys. The class defines an `apply` function for reading and decoding keys, similar to [`env`][env], [`prop`][prop], and [`file`][file] included in the core module.
+
+```tut:silent
+import ciris.{ConfigEntry, ConfigError, ConfigDecoder}
+
+final class PropFileAt(file: File, charset: Charset) {
+ private val propFile: Either[ConfigError, Map[String, String]] =
+ propFileSource
+ .read((file, charset))
+ .value
+
+ private def propFileKey(key: String): PropFileKey =
+ PropFileKey(file, charset, key)
+
+ private def propFileAt(key: String): Either[ConfigError, String] =
+ propFile.flatMap { props =>
+ props.get(key).toRight {
+ ConfigError.missingKey(
+ propFileKey(key),
+ propFileKeyType
+ )
+ }
+ }
+
+ def apply[Value](key: String)(
+ implicit decoder: ConfigDecoder[String, Value]
+ ): ConfigEntry[Id, PropFileKey, String, Value] = {
+ ConfigEntry(
+ propFileKey(key),
+ propFileKeyType,
+ propFileAt(key)
+ ).decodeValue[Value]
+ }
+
+ override def toString: String =
+ s"PropFileAt($file, $charset)"
+}
+```
+
+Finally, we define `propFileAt` as a convenience function for creating instances of `PropFileAt`.
+
+```tut:silent
+def propFileAt(
+ name: String,
+ charset: Charset = Charset.defaultCharset
+): PropFileAt = {
+ new PropFileAt(new File(name), charset)
+}
+```
+
+We can then use the newly defined property file source as follows.
+
+```tut:book
+import eu.timepit.refined.types.net.UserPortNumber
+import ciris.refined._
+
+val propFile = propFileAt(tempFileName)
+
+propFile[UserPortNumber]("port")
+```
+
+## Suspending Effects
+Since reading the property file contents isn't _pure_, we could make use of [effect types](/docs/basics#suspending-effects) to suspend the reading of the file. We start by extracting the reading of the property file to outside of `PropFileAt`, so it doesn't have to deal with effects explicitly. `PropFileAt` now simply accepts the property file as an argument with context `F`. We require that there is a [`Monad`][Monad] instance available for `F`, since the [`ConfigDecoder`][ConfigDecoder] requires it for [`decodeValue`][decodeValue].
+
+```tut:silent
+import ciris.api.Monad
+import ciris.api.syntax._
+
+final class PropFileAt[F[_]: Monad](
+ file: File,
+ charset: Charset,
+ propFile: F[Either[ConfigError, Map[String, String]]]
+) {
+ private def propFileKey(key: String): PropFileKey =
+ PropFileKey(file, charset, key)
+
+ private def propFileAt(key: String): F[Either[ConfigError, String]] =
+ propFile.map { errorOrProps =>
+ errorOrProps.flatMap { props =>
+ props.get(key).toRight {
+ ConfigError.missingKey(
+ propFileKey(key),
+ propFileKeyType
+ )
+ }
+ }
+ }
+
+ def apply[Value](key: String)(
+ implicit decoder: ConfigDecoder[String, Value]
+ ): ConfigEntry[F, PropFileKey, String, Value] = {
+ ConfigEntry
+ .applyF(
+ propFileKey(key),
+ propFileKeyType,
+ propFileAt(key)
+ )
+ .decodeValue[Value]
+ }
+
+ override def toString: String =
+ s"PropFileAt($file, $charset, $propFile)"
+}
+```
+
+With the property file reading extracted, we can now define `propFileAtF`, which suspends the reading of the property file into context `F`. We would also like that the file is not read more than once, so we also need to memoize the result. The [cats-effect](/docs/cats-effect-module) module provides a [`suspendMemoizeF`][suspendMemoizeF] function on [`ConfigSource`][ConfigSource] with a syntax import, which creates a [`ConfigSource`][ConfigSource] with both suspended reading and memoized results. The function works on any context `F` for which there is a [`LiftIO`][LiftIO] instance defined.
+
+```tut:silent
+import cats.effect.LiftIO
+import ciris.cats.effect.syntax._
+
+def propFileAtF[F[_]: Monad: LiftIO](
+ name: String,
+ charset: Charset = Charset.defaultCharset
+): PropFileAt[F] = {
+ val file = new File(name)
+
+ val propFile =
+ propFileSource
+ .suspendMemoizeF[F]
+ .read((file, charset))
+ .value
+
+ new PropFileAt(file, charset, propFile)
+}
+```
+
+The `propFileAt` function can also be changed to use the new `PropFileAt` class.
+
+```tut:silent
+def propFileAt(
+ name: String,
+ charset: Charset = Charset.defaultCharset
+): PropFileAt[Id] = {
+ val file = new File(name)
+
+ val propFile =
+ propFileSource
+ .read((file, charset))
+ .value
+
+ new PropFileAt(file, charset, propFile)
+}
+```
+
+We can then use `propFileAtF` to read property file keys as follows.
+
+```tut:book
+import cats.effect.IO
+import ciris.cats.effect._
+
+val propFileF = propFileAtF[IO](tempFileName)
+
+propFileF[UserPortNumber]("port")
+```
+
+[decodeValue]: /api/ciris/ConfigEntry.html#decodeValue[A](implicitdecoder:ciris.ConfigDecoder[V,A],implicitmonad:ciris.api.Monad[F]):ciris.ConfigEntry[F,K,S,A]
+[ConfigDecoder]: /api/ciris/ConfigDecoder.html
+[Monad]: /api/ciris/api/Monad.html
+[env]: /api/ciris/index.html#env[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[prop]: /api/ciris/index.html#prop[Value](key:String)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,String,String,Value]
+[file]: /api/ciris/index.html#file[Value](file:java.io.File,modifyFileContents:String=>String,charset:java.nio.charset.Charset)(implicitdecoder:ciris.ConfigDecoder[String,Value]):ciris.ConfigEntry[ciris.api.Id,(java.io.File,java.nio.charset.Charset),String,Value]
+[ConfigKeyType]: /api/ciris/ConfigKeyType.html
+[ConfigKeyTypeFile]: /api/ciris/ConfigKeyType$.html#File:ciris.ConfigKeyType[(java.io.File,java.nio.charset.Charset)]
+[ConfigSource]: /api/ciris/ConfigSource.html
+[Sync]: /api/ciris/api/Sync.html
+[ConfigSourceFile]: /api/ciris/ConfigSource$.html#File
+[ConfigSourceCompanion]: /api/ciris/ConfigSource$.html
+[ConfigSourceCatchNonFatal]: /api/ciris/ConfigSource$.html#catchNonFatal[K,V](keyType:ciris.ConfigKeyType[K])(read:K=>V):ciris.ConfigSource[ciris.api.Id,K,V]
+[suspendF]: /api/ciris/ConfigSource.html#suspendF[G[_]](implicitevidence$1:ciris.api.Sync[G],implicitf:F~>G):ciris.ConfigSource[G,K,V]
+[LiftIO]: https://github.com/typelevel/cats-effect/blob/master/core/shared/src/main/scala/cats/effect/LiftIO.scala
+[suspendMemoizeF]: /api/ciris/cats/effect/syntax$$CatsEffectConfigSourceIdSyntax.html#suspendMemoizeF[F[_]](implicitevidence$1:ciris.api.Apply[F],implicitevidence$2:cats.effect.LiftIO[F]):ciris.ConfigSource[F,K,V]
diff --git a/docs/src/main/tut/docs/supporting-new-types.md b/docs/src/main/tut/docs/supporting-new-types.md
new file mode 100644
index 00000000..b29ed6d3
--- /dev/null
+++ b/docs/src/main/tut/docs/supporting-new-types.md
@@ -0,0 +1,54 @@
+---
+layout: docs
+title: "Supporting New Types"
+permalink: /docs/supporting-new-types
+---
+
+# Supporting New Types
+There may be times when you need to write decoders for custom types, when the type isn't already supported. Decoders are represented with [`ConfigDecoder`][ConfigDecoder]s and include several combinators for creating new decoders from existing ones. The [companion object][ConfigDecoderCompanion] of [`ConfigDecoder`][ConfigDecoder] also contain several functions for helping you create new decoders. Following, we'll show how to write decoding support for a custom `Odd` type representing odd numbers.
+
+Let's say you're dealing with a sealed `Odd` class, where you can only construct instances using an `odd` method, which accepts an `Int` and returns an `Option[Odd]`. Note that an alternative way to represent `Odd` values is by using the `Odd` predicate from [refined](/docs/refined-module), which saves you having to write custom decoders. However, there may be times when you need to resort to custom types, so `Odd` is simply serving as an example here.
+
+```tut:silent
+sealed abstract case class Odd(value: Int)
+
+def odd(value: Int): Option[Odd] = {
+ Option(value)
+ .filter(_ % 2 == 1)
+ .map(new Odd(_) {})
+}
+```
+
+When we try to decode values of type `Odd`, we'll get an error at compile-time.
+
+```tut:book:fail
+{
+ import ciris.env
+ env[Odd]("SHLVL")
+}
+```
+
+As the error suggests, we'll have to define an implicit `ConfigDecoder[String, Odd]` instance and include it in scope. We can take this one step further, by saying that if there is a `ConfigDecoder[A, Int]` instance available, we can provide a `ConfigDecoder[A, Odd]` instance. The [`mapOption`][mapOption] function on [`ConfigDecoder`][ConfigDecoder] can be used to convert the `Int` to an `Odd`. Most [`ConfigDecoder`][ConfigDecoder] functions accept a `typeName` argument, which is the name of the type we are attempting to decode. In this case, and in general when the type you're dealing with does not have type parameters, it's simple enough to provide the type name. When dealing with types with type parameters, you might have to resort to using [type tags][typeTags], depending on the platform you're running on (Scala, Scala.js, Scala Native).
+
+```tut:book
+import ciris.ConfigDecoder
+
+implicit def oddConfigDecoder[A](
+ implicit decoder: ConfigDecoder[A, Int]
+): ConfigDecoder[A, Odd] = {
+ decoder.mapOption("Odd")(odd)
+}
+```
+
+If we now try to decode an `Odd` value, we should no longer get an error at compile-time.
+
+```tut:book
+import ciris.env
+
+env[Odd]("SHLVL")
+```
+
+[ConfigDecoderCompanion]: /api/ciris/ConfigDecoder$.html
+[ConfigDecoder]: /api/ciris/ConfigDecoder.html
+[mapOption]: /api/ciris/ConfigDecoder.html#mapOption[C](typeName:String)(f:B=>Option[C]):ciris.ConfigDecoder[A,C]
+[typeTags]: https://docs.scala-lang.org/overviews/reflection/typetags-manifests.html
diff --git a/docs/src/main/tut/docs/topics.md b/docs/src/main/tut/docs/topics.md
new file mode 100644
index 00000000..547a0641
--- /dev/null
+++ b/docs/src/main/tut/docs/topics.md
@@ -0,0 +1,21 @@
+---
+layout: docs
+title: "Configuration Topics"
+position: 2
+permalink: /docs/topics
+---
+
+# Configuration Topics
+The following sections describe common topics when working with Ciris and configurations as code.
+
+- [Encoding Validation](/docs/validation) describes how to use [refinement types](/docs/refined-module) to encode validation in the types of your configuration, for increased safety and confidence in your configurations. Validating your configuration values is a critical factor in preventing latent errors, as being able to detect configuration errors early on can reduce failure damage[^1].
+- [Multiple Environments](/docs/environments) explains how to use [enumeratum](/docs/enumeratum-module) enumerations to model and work with multiple environments in your configurations. This includes being able to have different configuration values for different environments, and being able to change the configuration loading process depending on the environment.
+- [Logging Configurations](/docs/logging) shows how you can log your configurations for debug purposes, while not exposing any secret configuration values. This is done with a [`Secret`][Secret] wrapper type, and either just `print`, or with the [`Show`][Show] type class from [cats](/docs/cats-module), together with type class instance derivation from [kittens][kittens].
+
+---
+
+[^1]: For example, refer to the paper [Early Detection of Configuration Errors to Reduce Failure Damage](https://www.usenix.org/system/files/conference/osdi16/osdi16-xu.pdf) and Leif Wickland's presentation [Defusing the Configuration Time Bomb](http://leifwickland.github.io/presentations/configBomb/) on the subject of configuration errors and validation.
+
+[kittens]: https://github.com/milessabin/kittens
+[Secret]: /api/ciris/Secret.html
+[Show]: https://typelevel.org/cats/typeclasses/show.html
diff --git a/docs/src/main/tut/docs/validation.md b/docs/src/main/tut/docs/validation.md
new file mode 100644
index 00000000..408c4200
--- /dev/null
+++ b/docs/src/main/tut/docs/validation.md
@@ -0,0 +1,205 @@
+---
+layout: docs
+title: "Encoding Validation"
+permalink: /docs/validation
+---
+
+# Encoding Validation
+Ensuring that your configurations are valid can be a tricky challenge. What we're trying to avoid is _latent configuration errors_[^1] which occur because configuration values are not validated upfront. When trying to use these values, we realize they are unusable, potentially causing all sorts of problems. For example, as seen below, we might accidentally use weak secret keys in our production environment, or try to start our service on ports we should never occupy in the first place.
+
+Ciris approach to avoiding _latent configuration errors_ is to use more precise types for your configuration values, only allowing values which you know are _useable_ to exist in the application. Essentially, values are validated as they are loaded, as part of the configuration loading process, and you'll end up with a configuration you know is _useable_. As you'll see later on, determining what _useable_ means can be difficult on its own, and we'll discuss how to reason about the concept.
+
+The main thing to remember is that we're trying to prevent errors where possible, and reduce the possibility of errors where they cannot be fully prevented. Ideally, we want to make only valid configurations representable, and discover _invalid_ configuration values as early as possible. The ultimate goal is to make working with configurations more safe.
+
+## Precise Configurations
+One challenge with loading configuration values is that most values are interpreted as `String`s, but that's rarely the type we want, or should, use to represent values. For example, you probably don't want to use any `String` as an API key (surely not the empty `String`, and not too weak keys), and not `String` or any `Int` for the port number (many port numbers are reserved or require sudo permissions to use).
+
+Ciris encourages you to encode validation by using more precise types, and integrates with several external libraries, like [enumeratum](/docs/enumeratum-module), [refined](/docs/refined-module), and [squants](/docs/squants-module), to be able to decode values into types provided by those libraries. One of the easiest and most convenient ways to use more precise types, is to use [refined](/docs/refined-module) and refinement types.
+
+Using refinement types, we can create a type which _refines_ an existing base type by applying a predicate type, which represents the validation logic. For example, we could express a type `ApiKey`, which, in this case, is any `String` with a length between 25 and 40 characters, and which only contains alphanumeric characters.
+
+```tut:silent
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.string.MatchesRegex
+import eu.timepit.refined.W
+
+type ApiKey = String Refined MatchesRegex[W.`"[a-zA-Z0-9]{25,40}"`.T]
+```
+
+By using the `ApiKey` type instead of `String` whenever we deal with an API key, we can now be confident that the value is not an invalid variant (like the empty `String`, or a too weak key, for example). Ciris integrates with refined, so you can load configuration values of type `ApiKey` without writing any additional code.
+
+```tut:book
+import ciris.{env, prop}
+import ciris.refined._
+
+env[ApiKey]("API_KEY").
+ orElse(prop[ApiKey]("api.key")).
+ orNone
+```
+
+Refinement types are also useful for ensuring that configuration values residing in code are valid. Thanks to [refined](/docs/refined-module) providing an `auto` macro, we can ensure that literal configuration values conform to their predicates at compile-time, and all we have to do is to use the appropriate import. Note that the actual `ApiKey` (or any other secret values) shouldn't be included in code, but rather loaded from, for example, a vault service. The `ApiKey` below could, for example, be used in local tests, and would there not be seen as a secret, and could therefore reside in code.
+
+```tut:book
+import eu.timepit.refined.auto._
+
+val apiKey: ApiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX"
+```
+
+If the `ApiKey` is not valid, we'll get an error at compile-time.
+
+```tut:fail
+val apiKey: ApiKey = "changeme"
+```
+
+If we need to use libraries which doesn't support our `ApiKey` type, we can retrieve the underlying `String` value.
+
+```tut:book
+apiKey.value
+```
+
+Also, if we want to avoid accidentally logging secrets, we can use [`Secret`][Secret].
+
+```tut:book
+import ciris.Secret
+
+env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")).
+ orNone
+```
+
+For more information about [`Secret`][Secret] and logging, refer to the [logging configurations](/docs/logging) section.
+
+Refinement types are not limited to `String`s, and [refined](/docs/refined-module) already includes many common refinement types. One example is `UserPortNumber` for `Int`s representing port numbers in the closed interval 1024 to 49151. This is a more precise definition of port numbers than `Int`, and lets us avoid many reserved port numbers.
+
+```tut:book
+import eu.timepit.refined.types.net.UserPortNumber
+
+env[UserPortNumber]("PORT").
+ orElse(prop[UserPortNumber]("http.port")).
+ orNone
+```
+
+Putting everything together, we're left with a more precise configuration, with validation encoded in the types.
+
+```tut:silent
+import eu.timepit.refined.types.numeric.PosInt
+import eu.timepit.refined.types.string.NonEmptyString
+
+final case class ApiConfig(
+ key: Secret[ApiKey],
+ port: UserPortNumber,
+ timeoutSeconds: PosInt
+)
+
+final case class Config(
+ appName: NonEmptyString,
+ api: ApiConfig
+)
+```
+
+The literal, and default, configuration values are also validated at compile-time. Ciris helps you load refinement types without having to write any additional code, and we've already drastically reduced the risk of _latent configuration errors_.
+
+```tut:book
+import ciris.loadConfig
+
+val config =
+ loadConfig(
+ env[Secret[ApiKey]]("API_KEY").
+ orElse(prop[Secret[ApiKey]]("api.key")),
+ prop[Option[UserPortNumber]]("http.port")
+ ) { (apiKey, port) =>
+ Config(
+ appName = "my-api",
+ api = ApiConfig(
+ key = apiKey,
+ timeoutSeconds = 10,
+ port = port getOrElse 4000
+ )
+ )
+ }
+```
+
+## Useable Configurations
+An interesting question arises when using refinement types: how far should we go to ensure that our configuration values are _useable_? For example, despite having restricted port numbers to `UserPortNumber`s, there is nothing that guarantees that the specified port is actually available, as another service might already be using the port. Being familiar with refinement types, you might be tempted to write an `OpenPort` predicate, which checks whether the port is open or not by creating a socket and immediately closing it.
+
+```tut:silent
+import eu.timepit.refined.api.Validate
+import java.net.ServerSocket
+
+final case class OpenPort()
+
+implicit val openPortValidate: Validate.Plain[Int, OpenPort] =
+ Validate.fromPartial(new ServerSocket(_).close(), "OpenPort", OpenPort())
+```
+
+We'll then check whether some `Int`s conform to the `OpenPort` predicate.
+
+```tut:book
+import eu.timepit.refined.refineV
+
+// System port number, requires sudo permissions
+refineV[OpenPort](989)
+
+// User port number, can be used, and is not already used
+refineV[OpenPort](10000)
+
+// Port number outside range, cannot be used
+refineV[OpenPort](65536)
+```
+
+While this might seem like a good idea at first, when used in conjunction with the `auto` macro, for compile-time safe literal configuration values, we are actually performing the `OpenPort` check during compile-time. This means that the port values you specify in code, need to be open on the machine compiling the code, which is not what you would expect.
+
+```tut:fail
+val port: Int Refined OpenPort = 989
+```
+
+Maybe it's not such a good idea to use _impure_ functions in our predicates. There are still some configuration values for which we'll have to guard against errors when using the values (binding a port number, for example). However, we can still reduce the possibility of errors by being more precise in the definition of the values. For port numbers, for example, it means that we can prevent attempts to use _unuseable_ port number at compile-time (for port numbers specified in code), or as part of the configuration loading process (for port numbers loaded from the environment). If we're able to detect unuseable configuration values as early as at compile-time, or during configuration loading, we've saved valuable time by preventing errors as early as possible.
+
+In general, it's recommended to only use _pure_ functions in predicates, and to try and be as precise as is practically possible when defining configuration value types -- you'll have to use your own judgement when it comes to this. It might take considerable effort to create very precise predicate types, but it can also pay off in terms of fewer errors and failures. Sometimes it is enough to use a more precise type than you normally would, for example `NonEmptyString` instead of `String`, which might not be as precise as possible, but still eliminates some invalid variants.
+
+## External Libraries
+When interacting with other libraries, you'll often see uses of imprecise types, like `String`, even though a more precise type is expected. Often there is validation logic behind the scenes, which can be extracted to a predicate type, to avoid unexpected errors. An example is the name of a [Kafka](https://kafka.apache.org) topic, where Kafka libraries typically accept a `String` for the topic name, but checks to ensure that it follows some validation rules. Depending on the library, these rules may or may not be well documented, and sometimes you'll have to dive into the [code](https://github.com/apache/kafka/blob/6cfcc9d553622e7d511a849935e9b504f947399d/clients/src/main/java/org/apache/kafka/common/internals/Topic.java) to find them.
+
+For reference, following is an example of how to express the Kafka topic name validation rules.
+
+```tut:silent
+def isKafkaTopicName(topic: String): Boolean =
+ 1 <= topic.size && topic.size <= 249 && (
+ topic != "." && topic != ".." && (
+ topic.forall(c => c.isLetterOrDigit || c == '.' || c == '_' || c == '-')
+ ))
+```
+
+For comparison, following is an example of how to express the validation rules with refinement types.
+
+```tut:silent
+import eu.timepit.refined.boolean.{And, Not, Or}
+import eu.timepit.refined.char.LetterOrDigit
+import eu.timepit.refined.collection.{Forall, Size}
+import eu.timepit.refined.generic.Equal
+import eu.timepit.refined.numeric.Interval
+
+type KafkaTopicName = String Refined
+ And[Size[Interval.Closed[W.`1`.T, W.`249`.T]],
+ And[Not[Equal[W.`"."`.T]],
+ And[Not[Equal[W.`".."`.T]],
+ Forall[Or[LetterOrDigit,
+ Or[Equal[W.`'.'`.T],
+ Or[Equal[W.`'_'`.T],
+ Equal[W.`'-'`.T]]]]]]]]
+```
+
+Note the similarities between working at the value-level with `isKafkaTopicName`, and representing the same validation rules at the type-level with `KafkaTopicName`. While the type signature above might look complicated at first glance, there is quite often a straightforward translation between validation rules at the value-level and the equivalent rules at the type-level. Note that we instead could have chosen to represent the rules with a regular expression, both at the value-level and type-level (using the `MatchesRegex` predicate).
+
+Kafka topic names are generally not secret, and can therefore reside as configuration values in code. With the refinement type `KafkaTopicName`, we benefit from being able to validate our Kafka topic names at compile-time, meaning we can be sure at compile-time that our topic names are _useable_.
+
+```tut:book
+val kafkaTopicName: KafkaTopicName = "my-topic-v2"
+```
+
+---
+
+[^1]: For more information on latent configuration errors, refer to the paper [Early Detection of Configuration Errors to Reduce Failure Damage](https://www.usenix.org/system/files/conference/osdi16/osdi16-xu.pdf) and Leif Wickland's presentation [Defusing the Configuration Time Bomb](http://leifwickland.github.io/presentations/configBomb/) on the subject.
+
+[refined]: https://github.com/fthomas/refined
+[Secret]: /api/ciris/Secret.html
diff --git a/docs/src/main/tut/docs/validation/readme.md b/docs/src/main/tut/docs/validation/readme.md
deleted file mode 100644
index 695f4dfc..00000000
--- a/docs/src/main/tut/docs/validation/readme.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-layout: docs
-title: "Encoding Validation"
-position: 4
-permalink: /docs/validation
----
-
-# Encoding Validation
-Ciris intentionally forces you to encode validation in your data types. This means that you have to put more thought into your configurations, and in turn, your domain models. For example, if you want to load an API key, you probably don't want it to be any `String` (not empty) and if you want to load a port value, you probably don't want it to be any `Int` (valid ports are 0 to 65535).
-
-By using more precise types, we get type-safety and a guarantee that values conform to our requirements throughout the application. One of the easiest ways to encode validation in data types is by using [refined][refined]. Ciris provides a `ciris-refined` module which allows you to read any refined type. You can also get compile-time validation for literals from refined (supported by macros).
-
-Let's modify the configuration seen in [Usage Basics](/docs/basics) to use refined types.
-
-```tut:book
-import eu.timepit.refined.auto._
-import eu.timepit.refined.types.net.PortNumber
-import eu.timepit.refined.types.string.NonEmptyString
-
-import scala.concurrent.duration._
-
-final case class Config(
- apiKey: NonEmptyString,
- timeout: Duration,
- port: PortNumber
-)
-```
-
-Here we've said that the API key must be a non-empty `String` and that the port number must be an `Int` between 0 and 65535. We could also require that the timeout must be finite and within a certain range, but since it's always specified in the code, it's arguably harder to get wrong. Now, let's see how we can simply change the types to read in the `env` and `prop` methods to the refined types, and how Ciris will take care of loading them for you.
-
-```tut:book
-import ciris._
-import ciris.refined._
-
-val config =
- loadConfig(
- env[NonEmptyString]("API_KEY"),
- prop[Option[PortNumber]]("http.port")
- ) { (apiKey, port) =>
- Config(
- apiKey = apiKey,
- timeout = 10 seconds,
- port = port getOrElse 4000
- )
- }
-```
-
-[refined]: https://github.com/fthomas/refined
diff --git a/docs/src/main/tut/index.md b/docs/src/main/tut/index.md
index c9753d8d..f4ae5ba6 100644
--- a/docs/src/main/tut/index.md
+++ b/docs/src/main/tut/index.md
@@ -21,7 +21,7 @@ val latestMinorVersion =
[](https://typelevel.org/projects/#ciris) [](https://travis-ci.org/vlovgr/ciris) [](https://codecov.io/gh/vlovgr/ciris) [](https://gitter.im/vlovgr/ciris) [](https://index.scala-lang.org/vlovgr/ciris)
-## Ciris
+## Ciris
Lightweight, extensible, and validated configuration loading in [Scala][scala], [Scala.js][scalajs], and [Scala Native][scalanative].
The core library is dependency-free, while modules provide integrations with external libraries.
@@ -33,12 +33,12 @@ Ciris' logo was inspired by the epyllion Ciris from [Appendix Vergiliana](https:
Ciris is a new project under active development. Feedback and contributions are welcome.
-### Introduction
-Ciris encourages compile-time safety by defining as much as possible of your configurations in Scala. For the data which cannot reside in code, Ciris helps you to load and decode values, while dealing with errors. Validation is encoded by using appropriate data types, with available integrations to libraries such as [cats][cats], [enumeratum][enumeratum], [refined][refined], [spire][spire], and [squants][squants].
+### Introduction
+Ciris is a _configuration as code_ library for compile-time safe configurations. For the configuration values which cannot reside in code, Ciris helps you to load and decode values from various sources, while dealing with effects and errors. Validation is encoded by using appropriate data types, with integrations to libraries such as [enumeratum][enumeratum], [refined][refined], [spire][spire], and [squants][squants].
-Ciris is intended as an alternative to configuration files, and libraries like [Lightbend Config](https://github.com/lightbend/config), in situations where it's easy to change and deploy software. Ciris aims to make it easy, safe, and secure to work with configurations, by eliminating many common configuration errors, by preventing errors from occurring as early as possible, and by loading secret configuration values directly from vault services (like, for example, [Kubernetes Secrets][ciris-kubernetes] and [AWS Systems Manager][ciris-aws-ssm]).
+Ciris is an alternative to configuration files in situations where it's easy to change and deploy software. Ciris aims to make it easy, safe, and secure to work with configurations, by eliminating many common configuration errors, by preventing errors from occurring as early as possible, and by loading secret configuration values directly from vault services.
-The [usage guide](https://cir.is/docs) provides a more detailed introduction to Ciris. See also the presentation [Refined types for validated configurations](https://www.youtube.com/watch?v=C3ciegxMAqA) and follow-up blog post [Validated Configurations with Ciris](https://typelevel.org/blog/2017/06/21/ciris.html) for a short introduction to the library and configurations with refined types.
+For a more detailed introduction, please refer to the [usage guide](https://cir.is/docs).
@@ -46,7 +46,7 @@ The [usage guide](https://cir.is/docs) provides a more detailed introduction to
-### Getting Started +### Getting Started To get started with [SBT][sbt], simply add the following lines to your `build.sbt` file. For an overview, usage instructions, and examples, please see the [usage guide](https://cir.is/docs). @@ -99,10 +99,10 @@ s""" ``` The only required module is `ciris-core`, the rest are optional library integrations. -For an explanation of how to use the modules, see the [Modules Overview](https://cir.is/docs/modules) section. +For an explanation of how to use the modules, refer to the [modules overview](https://cir.is/docs/modules) section. -- The `ciris-cats` module provides typeclasses and typeclass instances from [cats][cats]. -- The `ciris-cats-effect` module provides typeclasses for effect types from [cats-effect][cats-effect]. +- The `ciris-cats` module provides type classes and type class instances from [cats][cats]. +- The `ciris-cats-effect` module provides effect type classes from [cats-effect][cats-effect]. - The `ciris-enumeratum` module allows loading [enumeratum][enumeratum] enumerations. - The `ciris-generic` module allows loading more types with [shapeless][shapeless]. - The `ciris-refined` module allows loading [refined][refined] refinement types. @@ -121,13 +121,13 @@ If you're using `ciris-cats` or `ciris-cats-effect` with Scala 2.11.9 or later, scalacOptions += "-Ypartial-unification" ``` -or, if you need to support Scala 2.10.6 or later, you can use the [sbt-partial-unification](https://github.com/fiadliel/sbt-partial-unification#sbt-partial-unification) plugin. +or, if you need to support Scala 2.10.6 or later, you can use the [sbt-partial-unification](https://github.com/fiadliel/sbt-partial-unification) plugin. ```scala addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.0") ``` -#### Ammonite +#### Ammonite To start an [Ammonite REPL](http://www.lihaoyi.com/Ammonite/#Ammonite-REPL) with Ciris loaded and imported, simply run the following. ```bash curl -Ls try.cir.is | sh @@ -160,7 +160,7 @@ s""" ) ``` -#### External Libraries +#### External Libraries Below is an incomplete list of third-party libraries that integrate with Ciris. If your library is not included in the list, then please open a pull request. @@ -169,16 +169,16 @@ If your library is not included in the list, then please open a pull request. * [`ciris-credstash`][ciris-credstash] * [`ciris-kubernetes`][ciris-kubernetes] -### Documentation +### Documentation For an overview, with examples and explanations of the most common use cases, please refer to the [usage guide](https://cir.is/docs). If you're looking for a more detailed code-centric overview, you can instead take a look at the [API documentation](https://cir.is/api). -### Participation +### Participation Ciris embraces pure, typeful, idiomatic functional programming in Scala, and wants to provide a safe and friendly environment for teaching, learning, and contributing as described in the [Typelevel Code of Conduct](https://typelevel.org/conduct.html). It is expected that participants follow the code of conduct in all official channels, including on GitHub and in the Gitter chat room. If you would like to be involved in building Ciris, check out the [contributing guide](https://cir.is/docs/contributing). -### License +### License Ciris is available under the MIT license, available at [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) and in the [license file](https://github.com/vlovgr/ciris/blob/master/license.txt). [cats]: https://github.com/typelevel/cats