Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ZIO Config Documentation #1416

Open
wants to merge 14 commits into
base: series/4.x
Choose a base branch
from
19 changes: 15 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,16 @@ lazy val examples = crossProject(JVMPlatform)
})
.value
)
.dependsOn(zioConfig, zioConfigMagnolia, zioConfigRefined, zioConfigTypesafe, zioConfigYaml)
.dependsOn(
zioConfig,
zioConfigMagnolia,
zioConfigRefined,
zioConfigTypesafe,
zioConfigYaml,
zioConfigEnumeratum,
zioConfigScalaz,
zioConfigCats
)

lazy val examplesJVM = examples.jvm

Expand Down Expand Up @@ -436,8 +445,7 @@ lazy val docs = project
zioConfigTypesafeJVM,
zioConfigDerivationJVM,
zioConfigYamlJVM,
zioConfigRefinedJVM,
zioConfigMagnoliaJVM
zioConfigRefinedJVM
)
)
.settings(macroDefinitionSettings)
Expand All @@ -447,6 +455,9 @@ lazy val docs = project
zioConfigDerivationJVM,
zioConfigYamlJVM,
zioConfigRefinedJVM,
zioConfigMagnoliaJVM
zioConfigMagnoliaJVM,
zioConfigEnumeratumJVM,
zioConfigScalazJVM,
zioConfigCatsJVM
)
.enablePlugins(WebsitePlugin)
81 changes: 81 additions & 0 deletions docs/auto-generation-of-config-documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
id: auto-generation-of-config-documentation
title: "Auto-generation of Config Documentation"
---

ZIO Config has a built-in support for generating documentation for the configuration descriptors. This feature is useful for library authors to provide documentation for their configuration stuff. No matter how we have defined our config descriptors, manually or automatically using magnolia, we can generate documentation for them.

## Example 1: Simple Configuration

```scala mdoc:silent
import utils._

printSource("examples/shared/src/main/scala/zio/config/examples/documentation/GeneratingConfigDocumentation.scala")
```

Here is the output:

```md
auto-generated documentation of MyConfig:

## Configuration Details


|FieldName|Format |Description|Sources|
|--- |--- |--- |--- |
| |[all-of](fielddescriptions)| | |

### Field Descriptions

|FieldName|Format |Description |Sources|
|--- |--- |--- |--- |
|LDAP |primitive|a text property, Related to auth | |
|PORT |primitive|an integer property, Database port| |
|DB_URL |primitive|a text property, URL of database | |
```

Currently, ZIO Config supports generating the documentation in two flavors: GitHub and Confluence markdown.

## Example 2: Nested Configuration

Here is another example, which includes nested configuration values:

```scala mdoc:silent
import utils._
printSource("examples/shared/src/main/scala/zio/config/examples/documentation/NestedConfigDocumentation.scala")
```

Let's see how the documentation looks like:

```md
Auto-generated documentation of AppConfig:

## Configuration Details


|FieldName|Format |Description|Sources|
|--- |--- |--- |--- |
| |[all-of](fielddescriptions)| | |

### Field Descriptions

|FieldName |Format |Description |Sources|
|--- |--- |--- |--- |
|SECRET |primitive |a text property, Application secret| |
|[CREDENTIALS](credentials)|[all-of](credentials)|Credentials | |
|[DATABASE](database) |[all-of](database) |Database | |

### CREDENTIALS

|FieldName|Format |Description |Sources|
|--- |--- |--- |--- |
|USERNAME |primitive|a text property, Example: ZioUser| |
|PASSWORD |primitive|a text property, Example: ZioPass| |

### DATABASE

|FieldName|Format |Description |Sources|
|--- |--- |--- |--- |
|PORT |primitive|an integer property, Example: 8088| |
|URL |primitive|a text property, Example: abc.com | |
```
1 change: 0 additions & 1 deletion docs/automatic-validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Take a look at `zio.config.refined` package.
import zio.Config
import zio.ConfigProvider
import zio.config._, refined._

```

A few examples are given below.
Expand Down
151 changes: 151 additions & 0 deletions docs/defining-config-descriptors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
id: defining-config-descriptors
title: "Defining Config Descriptors"
---

ZIO Config uses the `Config[A]` to describe the configuration of type `A`, which is part of ZIO core library. So before diving into ZIO Config, we need to understand the `Config[A]` data type. There is a [dedicated section](https://zio.dev/reference/configuration/) in the ZIO documentation that explains what are config descriptors and how we can create them.

## Defining Config Descriptors

There are two ways to create ZIO config descriptors:
1. **Manual Definition of Configuration Descriptors** — We can manually create a configuration descriptor using the `Config` data type and its compositional operators.
2. **Auto-derivation of Configuration Descriptors** — We can derive a configuration descriptor for a case class or sealed trait using the `zio-config-magnolia` module.

Let's talk about both of these methods in detail.

### Manual Definition of Config Descriptors

We must fetch the configuration from the environment to a case class (product) in scala. Let it be `MyConfig`

```scala mdoc:silent
case class MyConfig(ldap: String, port: Int, dburl: String)
```

To perform any action using ZIO Config, we need a configuration description. Let's define a simple one. To generate a `Config[MyConfig]` we can first generate tuples of the primitive configurations like `string`, `int`, etc using the `zip` operator, then map them to their respective case class:

```scala mdoc:silent
import zio._
import zio.config._
import zio.ConfigProvider
import zio.Config, Config._

object MyConfig {
val config: Config[MyConfig] = (string("LDAP") zip int("PORT") zip string("DB_URL")).to[MyConfig]
}
```

There are several other combinators which can be used to describe the configuration. To learn more please refer to the ZIO core reference section for [configuration](https://zio.dev/reference/configuration/).

### Auto-derivation of Config Descriptors

If we don't like describing our configuration manually, we can use the `zio-config-magnolia` module to derive the configuration descriptor for a case class or a sealed trait. Let's add this module to our `build.sbt` file:

```scala
libraryDependencies += "dev.zio" %% "zio-config-magnolia" % "@VERSION@"
```

By importing the `zio.config.magnolia._` package, we can derive the configuration descriptor for a case class or a sealed trait using the `deriveConfig` method:

```scala mdoc:silent:nest
import zio.config._
import zio.config.magnolia._

case class MyConfig(ldap: String, port: Int, dburl: String)

object MyConfig {
implicit val config: Config[MyConfig] = deriveConfig[MyConfig]
}
```

## Accumulating all errors

For any misconfiguration, the `ReadError` collects all of them with proper semantics: `AndErrors` and `OrErrors`.
Instead of directly printing misconfigurations, the `ReadError.prettyPrint` shows the path, detail of collected misconfigurations.

1. All misconfigurations of `AndErrors` are put in parallel lines.

```text
╠══╗
║ ║ FormatError
║ MissingValue
```

2. `OrErrors` are in the same line which indicates a sequential misconfiguration

```text
╠MissingValue
╠FormatError
```

Here is a complete example:

```text
ReadError:
╠══╦══╗
║ ║ ║
║ ║ ╠─MissingValue
║ ║ ║ path: var2
║ ║ ║ Details: value of type string
║ ║ ║
║ ║ ╠─MissingValue path: envvar3
║ ║ ║ path: var3
║ ║ ║ Details: value of type string
║ ║ ║
║ ║ ▼
║ ║
║ ╠─FormatError
║ ║ cause: Provided value is wrong, expecting the type int
║ ║ path: var1
║ ▼
```

## Operations

### Mapping keys

Now on, the only way to change keys is as follows:

```scala
// mapKey is just a function in `Config` that pre-existed

val config = deriveConfig[Config].mapKey(_.toUpperCase)
```

### CollectAll

```scala mdoc:compile-only
import zio._
import zio.config._

final case class Variables(variable1: Int, variable2: Option[Int])

val listOfConfig: List[Config[Variables]] =
List("GROUP1", "GROUP2", "GROUP3", "GROUP4")
.map(group => (Config.int(s"${group}_VARIABLE1") zip Config.int(s"${group}_VARIABLE2").optional).to[Variables])

val configOfList: Config[List[Variables]] =
Config.collectAll(listOfConfig.head, listOfConfig.tail: _*)
```

### orElseEither && Constant

```scala mdoc:compile-only
import zio._
import zio.config._

sealed trait Greeting

case object Hello extends Greeting
case object Bye extends Greeting

val configSource =
ConfigProvider.fromMap(Map("greeting" -> "Hello"))

val config: Config[String] =
Config.constant("Hello").orElseEither(Config.constant("Bye")).map(_.merge)
```
Loading
Loading