diff --git a/build.sbt b/build.sbt index 308f00f59..ac53bcc8a 100644 --- a/build.sbt +++ b/build.sbt @@ -276,7 +276,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 @@ -454,8 +463,7 @@ lazy val docs = project zioConfigTypesafeJVM, zioConfigDerivationJVM, zioConfigYamlJVM, - zioConfigRefinedJVM, - zioConfigMagnoliaJVM + zioConfigRefinedJVM ) ) .settings(macroDefinitionSettings) @@ -465,7 +473,10 @@ lazy val docs = project zioConfigDerivationJVM, zioConfigYamlJVM, zioConfigRefinedJVM, - zioConfigMagnoliaJVM + zioConfigMagnoliaJVM, + zioConfigEnumeratumJVM, + zioConfigScalazJVM, + zioConfigCatsJVM ) .enablePlugins(WebsitePlugin) diff --git a/docs/auto-generation-of-config-documentation.md b/docs/auto-generation-of-config-documentation.md new file mode 100644 index 000000000..81089fc51 --- /dev/null +++ b/docs/auto-generation-of-config-documentation.md @@ -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 | | +``` diff --git a/docs/automatic-validations.md b/docs/automatic-validations.md index d8323edf1..2f801cba4 100644 --- a/docs/automatic-validations.md +++ b/docs/automatic-validations.md @@ -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. diff --git a/docs/defining-config-descriptors.md b/docs/defining-config-descriptors.md new file mode 100644 index 000000000..46f03dc3d --- /dev/null +++ b/docs/defining-config-descriptors.md @@ -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) +``` diff --git a/docs/dive-into-zio-config.md b/docs/dive-into-zio-config.md deleted file mode 100644 index f07d41dcf..000000000 --- a/docs/dive-into-zio-config.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -id: dive-into-zio-config -title: "Dive Into ZIO Config" ---- - -__Note that this documentation is for 1.x series. For newer versions, please refer to [docs](https://github.com/zio/zio-config/tree/master/docs) section in GitHub.__ - -## Describe the config by hand - -We must fetch the configuration from the environment to a case class (product) in scala. Let it be `MyConfig` - -```scala mdoc:silent -import zio.IO - -import zio.config._ -import zio.ConfigProvider -import zio.Config, Config._ - -``` - -```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. - - -```scala mdoc:silent -val myConfig: Config[MyConfig] = - (string("LDAP") zip int("PORT") zip string("DB_URL")).to[MyConfig] - - // Config[MyConfig] -``` - -To get a tuple, - -```scala mdoc:silent -val myConfigTupled: Config[(String, Int, String)] = - (string("LDAP") zip int("PORT") zip string("DB_URL")) -``` - -## Fully automated Config Description - -If you don't like describing your configuration manually, and rely on the names of the parameter in the case class (or sealed trait), -there is a separate module called `zio-config-magnolia`. - -Note: `zio-config-shapeless` is an alternative to `zio-config-magnolia` to support scala 2.11 projects. -It will be deprecated once we find users have moved on from scala 2.11. - - -```scala mdoc:silent -import zio.config._ -import zio.config.magnolia._ - -val myConfigAutomatic = deriveConfig[MyConfig] -``` - -`myConfig` and `myConfigAutomatic` are same description, and is of the same type. - -Refer to API docs for more explanations on [descriptor](https://javadoc.io/static/dev.zio/zio-config-magnolia_2.13/1.0.0-RC31-1/zio/config/magnolia/index.html#descriptor[A](implicitconfig:zio.config.magnolia.package.Descriptor[A]):zio.Config[A]) -More examples on automatic derivation is in examples module of [zio-config](https://github.com/zio/zio-config) - -## Read config from various sources - -There are more information on various sources in [here](read-from-various-sources.md). - -Below given is a simple example. - -```scala mdoc:silent -val map = - Map( - "LDAP" -> "xyz", - "PORT" -> "8888", - "DB_URL" -> "postgres" - ) - -val source = ConfigProvider.fromMap(map) - -source.load(myConfig) - -``` - -### Documentations using Config - -```scala mdoc:silent -generateDocs(myConfig) -//Creates documentation (automatic) - -val betterConfig = - (string("LDAP") ?? "Related to auth" zip int("PORT") ?? "Database port" zip - string("DB_URL") ?? "url of database" - ).to[MyConfig] - -generateDocs(betterConfig).toTable.toGithubFlavouredMarkdown -// Custom documentation along with auto generated docs -``` - -### 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 - ║ ▼ - ▼ -``` - -### Example of 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) -``` - -## Inbuilt support for pure-config - -Many users make use of the label `type` in HOCON files to annotate the type of the coproduct. -Now on, zio-config has inbuilt support for reading such a file/string using `descriptorForPureConfig`. - - -```scala -import zio.config._, typesafe._, magnolia._ - -@nameWithLabel("type") -sealed trait X -case class A(name: String) extends X -case class B(age: Int) extends X - -case class AppConfig(x: X) - -val str = - s""" - x : { - type = A - name = jon - } - """ - -ConfigProvider.fromHoconString(str).load(deriveConfig[AppConfig]) - -``` - - -## The `to` method for easy manual configurations - -```scala -import zio.config._ -import zio.Config - -final case class AppConfig(port: Int, url: String) - -val config = Config.int("PORT").zip(Config.string("URL")).to[AppConfig] - -``` - -## A few handy methods - -### CollectAll - -```scala -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 -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[Greeting] = - Config.constant("Hello").orElseEither(Config.constant("Bye")).map(_.merge) - - -``` - diff --git a/docs/index.md b/docs/index.md index df6d12089..9643f4ceb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,7 +17,6 @@ Let's enumerate some key features of this library: - **Descriptive Errors** — It accumulates all errors and reports all of them to the user rather than failing fast. - **Integrations** — Integrations with a variety of libraries - If you are only interested in automatic derivation of configuration, find the details [here](https://zio.dev/zio-config/automatic-derivation-of-config) ## Installation @@ -25,18 +24,38 @@ If you are only interested in automatic derivation of configuration, find the de In order to use this library, we need to add the following line in our `build.sbt` file: ```scala -libraryDependencies += "dev.zio" %% "zio-config" % "" +libraryDependencies += "dev.zio" %% "zio-config" % "@VERSION@" ``` -# Quick Start +ZIO config has several modules that can be used based on the requirement. Here is the list of modules: + +```scala +libraryDependencies += "dev.zio" %% "zio-config-magnolia" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-typesafe" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-yaml" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-xml" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-pureconfig" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-aws" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-enumeratum" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-refined" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-derivation" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-cats" % "@VERSION@" +libraryDependencies += "dev.zio" %% "zio-config-scalaz" % "@VERSION@" +``` + +We will discuss these modules in detail in the following sections. + +## Example Let's add these four lines to our `build.sbt` file as we are using these modules in our examples: ```scala libraryDependencies += "dev.zio" %% "zio-config" % "" -libraryDependencies += "dev.zio" %% "zio-config-magnolia" % "" -libraryDependencies += "dev.zio" %% "zio-config-typesafe" % "" -libraryDependencies += "dev.zio" %% "zio-config-refined" % "" +libraryDependencies += "dev.zio" %% "zio-config-yaml" % "" // for reading yaml configuration files +libraryDependencies += "dev.zio" %% "zio-config-magnolia" % "" // for deriving configuration descriptions ``` -There are many examples in [here](https://github.com/zio/zio-config/tree/master/examples/shared/src/main/scala/zio/config/examples) +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/scala/zio/config/examples/configsources/YamlConfigReaderExample.scala") +``` diff --git a/docs/integrations.md b/docs/integrations.md index 193b1ebe1..cd76489fc 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -1,89 +1,73 @@ --- id: integrations -title: "Integrations" +sidebar_label: Integrations +title: "Integrations with other Libraries" --- +`zio-config` is also integrated with `enumeratum`, `cats`, `scalaz`, `aws-sdk`, `zio-aws`, `refined` etc. Note that only a few of them is documented here. `refined` is already discussed under `automatic-validations`. -## Integration with other libraries +## Enumeratum -`zio-config` is also integrated with `enumeratum`, `cats`, `scalaz`, `aws-sdk`, `zio-aws`, `refined` etc. -Note that only a few of them is documented here. `refined` is already discussed under `automatic-validations`. +Many applications rely on [this beautiful library](https://github.com/lloydmeta/enumeratum). ZIO Config can directly load it from enumeratum's `enum` without relying on auto-derivation (and rely on Enumeratum's macro indirectly with additional features): -#### Enumeratum +```scala mdoc:compile-only +import zio._ +import enumeratum.{Enum, EnumEntry} +import zio.config.enumeratum._ -Many applications rely on this beautiful library https://github.com/lloydmeta/enumeratum. -Zio-config can directly load it from enumeratum's `enum` without relying on auto-derivation (and rely on Enumeratum's macro indirectly witha additional features). +sealed trait Greeting extends EnumEntry -```scala +object Greeting extends Enum[Greeting] { + val values = findValues - sealed trait Greeting extends EnumEntry + case object Hello extends Greeting + case object GoodBye extends Greeting + case object Hi extends Greeting + case object Bye extends Greeting +} - object Greeting extends Enum[Greeting] { +val mapProvider = + ConfigProvider.fromMap(Map( + "greeting" -> "Hello" + )) - val values = findValues - - case object Hello extends Greeting - case object GoodBye extends Greeting - case object Hi extends Greeting - case object Bye extends Greeting - - } - - - // Load using zio-config - import zio.config.enumeratum._ - - val mapProvider = - ConfigProvider.fromMap(Map( - "greeting" -> "Hello" - )) - - val config = - `enum`(Greeting).nested("greeting") - - val pgm: IO[Error, Greeting] = - mapProvider.load(config) - - // Returns Hello - +val config = + `enum`(Greeting).nested("greeting") +val pgm: IO[Config.Error, Greeting] = mapProvider.load(config) +// Returns Hello ``` -#### Scalaz/Cats +## Scalaz/Cats -Highly polymorphic code end up relying on -typeclasses, and zio-config provides instances for `Config`. +Highly polymorphic code end up relying on typeclasses, and ZIO Config provides instances for `Config`. This is a simple example to showcase the capability. -```scala +```scala mdoc:compile-only +import zio._ +import _root_.scalaz._, Scalaz._ +import zio.config.scalaz.instances._ - import _root_.scalaz._, Scalaz._ - import zio.config.scalaz.instances._ - - // Across the application, there can be various effect types, but there is only one addition! - def add[F[_]: Applicative, A: Monoid](primary: F[A], secondary: F[A]): F[A] = - primary.<*>(Applicative[F].map(secondary)(secondary => (primary: A) => primary.mappend(secondary))) - - // Now even `Config` can take part in this addition given the values of config parameters should be Monoid, - // instead of using native `zip` and separately implementing addition for various types - val configResult = add(Config.int("marks1"), Config.int("marks2"))) - - ConfigProvider.fromMap(Map("marks1" -> "10", "marks2" -> "20")).load(configResult) // returns 30 - +// Across the application, there can be various effect types, but there is only one addition! +def add[F[_]: Applicative, A: Monoid](primary: F[A], secondary: F[A]): F[A] = + primary.<*>(Applicative[F].map(secondary)(secondary => (primary: A) => primary.mappend(secondary))) + +// Now even `Config` can take part in this addition given the values of config parameters should be Monoid, +// instead of using native `zip` and separately implementing addition for various types +val configResult = add(Config.int("marks1"), Config.int("marks2")) +ConfigProvider.fromMap(Map("marks1" -> "10", "marks2" -> "20")).load(configResult) // returns 30 ``` In addition to it, it can also load cats/scalaz specific datatypes -```scala - - import zio.config.scalaz._ - import _root_.scalaz.Maybe +```scala mdoc:compile-only +import zio._ +import zio.config.scalaz._ +import _root_.scalaz.Maybe - - val config: Config[Maybe[Int]] = maybe(Config.int("age")) - +val config: Config[Maybe[Int]] = maybe(Config.int("age")) ``` Have a look at modules of zio-config to know about other integrations such as `aws`, `zio-aws` etc diff --git a/docs/read-from-various-sources.md b/docs/read-from-various-sources.md index bf84f13fd..ebcdfc6a0 100644 --- a/docs/read-from-various-sources.md +++ b/docs/read-from-various-sources.md @@ -3,146 +3,134 @@ id: read-from-various-sources title: "Read from various Sources" --- -zio-config supports various sources. +ZIO Config supports various sources for reading configurations. In this guide, we will see how to read configurations from different sources such as in-memory maps, HOCON strings, files, YAML, and XML. -```scala mdoc:silent -import zio._, Config._, ConfigProvider._ -import zio.config._, magnolia._ -``` +## In-memory Map Source -```scala mdoc:silent -case class MyConfig(ldap: String, port: Int, dburl: String) +To load configs from an in-memory map, you can use `ConfigProvider.fromMap` method: + +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/scala/zio/config/examples/configsources/InmemoryMapSourceExample.scala") ``` -```scala mdoc:silent -val myConfig = - (string("LDAP") zip int("PORT") zip string("DB_URL")).to[MyConfig] +## Typesafe (HOCON) Config Source - // val automatedConfig = deriveConfig[MyConfig]; using zio-config-magnolia -``` +To enable HOCON source, we have to add the `zio-config-typesafe` module to our dependencies in `build.sbt` file: +```scala +libraryDependencies += "dev.zio" %% "zio-config-typesafe" % "@VERSION@" +``` -## HOCON String +By importing the `zio.config.typesafe._` module, we can read configs from HOCON sources. -To enable HOCON source, you have to bring in `zio-config-typesafe` module. -There are many examples in examples module in zio-config. +### HOCON String -Here is an quick example +We can use `ConfigProvider.fromHoconString` to load configs from a HOCON string: -```scala mdoc:silent -import zio.config.typesafe._ -import zio.config.magnolia._ +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconStringSourceExample.scala") ``` -```scala mdoc:silent -case class SimpleConfig(port: Int, url: String, region: Option[String]) +### HOCON File -val automaticDescription = deriveConfig[SimpleConfig] +Similar to the above example, we can read configs from a HOCON file, using `ConfigProvider.fromHoconFile`. -val hoconSource = - ConfigProvider.fromHoconString( - """ - { - port : 123 - url : bla - region: useast - } +Assume we have a HOCON file `application.simple.conf` in the `resources` directory: - """ - ) +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/resources/application.simple.conf") +``` +We can read the configuration file as follows: -val anotherHoconSource = - ConfigProvider.fromHoconString( - """ - port=123 - url=bla - region=useast - """ - ) +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconFileSourceExample.scala") +``` + +### JSON File -hoconSource.load(deriveConfig[SimpleConfig]) +We can use `zio-config-typesafe` module to fetch json as well. So let's add it to our `build.sbt` file: -// yielding SimpleConfig(123,bla,Some(useast)) +```scala +libraryDependencies += "dev.zio" %% "zio-config-typesafe" % "@VERSION@" ``` -## HOCON File +Assume we have a JSON file `application.json` in the `resources` directory: -```scala mdoc:silent -ConfigProvider.fromHoconFile(new java.io.File("fileapth")) +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/resources/application.json") ``` -## Json +We can read the configuration file as follows: -You can use `zio-config-typesafe` module to fetch json as well +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeJsonFileSourceExample.scala") +``` -```scala mdoc:silent -val jsonString = - """ - { - "port" : "123" - "url" : "bla" - "region": "useast" - } +## YAML Source - """ +Let's add these four lines to our `build.sbt` file as we are using these modules in our examples: -ConfigProvider.fromHoconString(jsonString) +```scala +libraryDependencies += "dev.zio" %% "zio-config" % "" +libraryDependencies += "dev.zio" %% "zio-config-yaml" % "" // for reading yaml configuration files +libraryDependencies += "dev.zio" %% "zio-config-magnolia" % "" // for deriving configuration descriptions ``` -## Yaml FIle - -Similar to Hocon source, we have `ConfigProvider.fromYamlString` +Assume we have the following configuration file: ```scala -import zio.config.yaml._ +import utils._ +printSource("examples/shared/src/main/resources/application.yml") +``` -ConfigProvider.fromYamlString +We can read the configuration file as follows: +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/scala/zio/config/examples/configsources/YamlConfigReaderExample.scala") ``` -## Xml String +Here is the output: -zio-config can read XML strings. Note that it's experimental with a dead simple native xml parser, -Currently it cannot XML comments, and has not been tested with complex data types, which will be fixed in the near future. - -```scala -import zio.config.xml.experimental._ -import zio.Config +``` +bootstrapServers: List(localhost:9092, locathost:9094) +region: US +port: 100 +``` -final case class Configuration(aws: Aws, database: Database) +## XML -object Configuration { - val config: Config[Configuration] = - Aws.config.nested("aws").zip(Database.config.nested("database")).to[Configuration].nested("config") +ZIO Config can read XML strings using the `zio-config-yaml` module, so we have to add the following line to our `build.sbt` file: - final case class Aws(region: String, account: String) +```scala +libraryDependencies += "dev.zio" %% "zio-config-yaml" % "@VERSION@" +``` - object Aws { - val config: Config[Aws] = Config.string("region").zip(Config.string("account")).to[Aws] - } - final case class Database(port: Int, url: String) +Note that it's experimental with a dead simple native xml parser, +Currently it cannot XML comments, and has not been tested with complex data types, which will be fixed in the near future. - object Database { - val config: Config[Database] = Config.int("port").zip(Config.string("url")).to[Database] - } -} +Assume we have the `application.xml` file in the `resources` directory: -val config = - s""" - | - | - | - | - | - |""".stripMargin +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/resources/application.xml") +``` -val parsed = ConfigProvider.fromYamlString(config).load(Configuration.config) +We can load the configuration file as follows: +```scala mdoc:passthrough +import utils._ +printSource("examples/shared/src/main/scala/zio/config/examples/configsources/YamlConfigReaderExample.scala") ``` - -### Indexed Map, Array datatype, and a some implementation notes +## Indexed Map, Array datatype, and a some implementation notes `zio-config` comes up with the idea of `IndexedFlat` allowing you to define indexed configs (see examples below). However, the constructors of `IndexedFlat` is not exposed to the user for the time being, since it can conflate with some ideas in `zio.core` `Flat`, @@ -154,14 +142,15 @@ See https://github.com/zio/zio/pull/7823 and https://github.com/zio/zio/pull/789 These changes are to keep the backward compatibility of ZIO library itself. -#### What does it mean to users? +### What does it mean to users? It implies, for sequence (or list) datatypes, you can use either `` or `""` to represent empty list in a flat structure. See the below example where it tries to mix indexing into flat structure. We recommend using `` over `""` whenever you are trying to represent a real indexed format Example: -```scala +```scala mdoc:compile-only +import zio._ import zio.config._, magnolia._ final case class Department(name: String, block: Int) @@ -179,21 +168,18 @@ val map = "employees[1].name" -> "foo", "employees[1].departments" -> "", ) - - -ConfigProvider.fromMap(map).load(derivedConfig[Config]) - +ConfigProvider.fromMap(map).load(deriveConfig[Config]) ``` Although we support indexing within Flat, formats such as Json/HOCON/XML is far better to work with indexing, and zio-config supports these formats making use of the above idea. -#### Another simple example of an indexed format - -```scala +### Another simple example of an indexed format +```scala mdoc:compile-only +import zio._ import zio.config._, magnolia._ final case class Employee(age: Int, name: String) @@ -212,5 +198,4 @@ final case class Employee(age: Int, name: String) val provider = ConfigProvider.fromMap(map) val config = Config.listOf("employees", deriveConfig[Employee]).nested("department") val result = provider.load(config) - ``` diff --git a/docs/sidebars.js b/docs/sidebars.js index d99fb10ec..f87bd75ba 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -6,7 +6,9 @@ const sidebars = { collapsed: false, link: { type: "doc", id: "index" }, items: [ - "dive-into-zio-config", + "index", + "defining-config-descriptors", + "auto-generation-of-config-documentation", "integrations", "automatic-derivation-of-config", "read-from-various-sources", diff --git a/examples/shared/src/main/resources/application.json b/examples/shared/src/main/resources/application.json new file mode 100644 index 000000000..99d542036 --- /dev/null +++ b/examples/shared/src/main/resources/application.json @@ -0,0 +1,5 @@ +{ + "port": "123", + "url": "bla", + "region": "useast" +} diff --git a/examples/shared/src/main/resources/application.simple.conf b/examples/shared/src/main/resources/application.simple.conf new file mode 100644 index 000000000..860ce3945 --- /dev/null +++ b/examples/shared/src/main/resources/application.simple.conf @@ -0,0 +1,3 @@ +port=123 +url=bla +region=useast diff --git a/examples/shared/src/main/resources/application.xml b/examples/shared/src/main/resources/application.xml new file mode 100644 index 000000000..728f98593 --- /dev/null +++ b/examples/shared/src/main/resources/application.xml @@ -0,0 +1,4 @@ + + + + diff --git a/examples/shared/src/main/scala/zio/config/examples/configsources/InmemoryMapSourceExample.scala b/examples/shared/src/main/scala/zio/config/examples/configsources/InmemoryMapSourceExample.scala new file mode 100644 index 000000000..335d992e5 --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/configsources/InmemoryMapSourceExample.scala @@ -0,0 +1,38 @@ +package zio.config.examples.configsources + +import zio._ +import zio.config.magnolia.deriveConfig + +object InmemoryMapSourceExample extends ZIOAppDefault { + + case class KafkaClients(bootstrapServers: List[String], port: Int, region: String) + + object KafkaClients { + implicit val config: Config[KafkaClients] = deriveConfig[KafkaClients] + } + + case class ApplicationConfig(kafkaClients: KafkaClients) + object ApplicationConfig { + implicit val config: Config[ApplicationConfig] = deriveConfig[ApplicationConfig] + } + + override val bootstrap = + Runtime.setConfigProvider( + ConfigProvider.fromMap( + Map( + "kafkaClients.bootstrapServers[0]" -> "foo", + "kafkaClients.bootstrapServers[1]" -> "bar", + "kafkaClients.port" -> "9092", + "kafkaClients.region" -> "EU" + ) + ) + ) + + def run = for { + config <- ZIO.config(ApplicationConfig.config) + _ <- ZIO.debug("bootstrapServers: " + config.kafkaClients.bootstrapServers) + _ <- ZIO.debug("region: " + config.kafkaClients.region) + _ <- ZIO.debug("port: " + config.kafkaClients.port) + } yield () + +} diff --git a/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconFileSourceExample.scala b/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconFileSourceExample.scala new file mode 100644 index 000000000..6937c679c --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconFileSourceExample.scala @@ -0,0 +1,30 @@ +package zio.config.examples.configsources + +import zio._ +import zio.config.magnolia.deriveConfig +import zio.config.typesafe.FromConfigSourceTypesafe + +import java.io.File + +object TypesafeHoconFileSourceExample extends ZIOAppDefault { + + case class SimpleConfig(port: Int, url: String, region: Option[String]) + + object SimpleConfig { + val config: Config[SimpleConfig] = deriveConfig[SimpleConfig] + } + + override val bootstrap = + Runtime.setConfigProvider( + ConfigProvider + .fromHoconFile(new File(getClass.getClassLoader.getResource("application.simple.conf").getFile)) + ) + + def run = + for { + config <- ZIO.config(SimpleConfig.config) + _ <- ZIO.debug("port: " + config.port) + _ <- ZIO.debug("url: " + config.url) + _ <- ZIO.debug("region: " + config.region) + } yield () +} diff --git a/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconStringSourceExample.scala b/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconStringSourceExample.scala new file mode 100644 index 000000000..5ad70aac9 --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeHoconStringSourceExample.scala @@ -0,0 +1,45 @@ +package zio.config.examples.configsources + +import zio._ +import zio.config.magnolia.deriveConfig +import zio.config.typesafe.FromConfigSourceTypesafe + +object TypesafeHoconStringSourceExample extends ZIOAppDefault { + + case class SimpleConfig(port: Int, url: String, region: Option[String]) + + object SimpleConfig { + val config: Config[SimpleConfig] = deriveConfig[SimpleConfig] + } + + override val bootstrap = + Runtime.setConfigProvider( + ConfigProvider + .fromHoconString( + """ + |{ + | port: 123 + | url: bla + | region: useast + |} + |""".stripMargin + ) + .orElse( + ConfigProvider.fromHoconString( + """ + |port=123 + |url=bla + |region=useast + |""".stripMargin + ) + ) + ) + + def run = + for { + config <- ZIO.config(SimpleConfig.config) + _ <- ZIO.debug("port: " + config.port) + _ <- ZIO.debug("url: " + config.url) + _ <- ZIO.debug("region: " + config.region) + } yield () +} diff --git a/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeJsonFileSourceExample.scala b/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeJsonFileSourceExample.scala new file mode 100644 index 000000000..44041d80f --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/configsources/TypesafeJsonFileSourceExample.scala @@ -0,0 +1,30 @@ +package zio.config.examples.configsources + +import zio._ +import zio.config.magnolia.deriveConfig +import zio.config.typesafe.FromConfigSourceTypesafe + +import java.io.File + +object TypesafeJsonFileSourceExample extends ZIOAppDefault { + + case class SimpleConfig(port: Int, url: String, region: Option[String]) + + object SimpleConfig { + val config: Config[SimpleConfig] = deriveConfig[SimpleConfig] + } + + override val bootstrap = + Runtime.setConfigProvider( + ConfigProvider + .fromHoconFile(new File(getClass.getClassLoader.getResource("application.json").getFile)) + ) + + def run = + for { + config <- ZIO.config(SimpleConfig.config) + _ <- ZIO.debug("port: " + config.port) + _ <- ZIO.debug("url: " + config.url) + _ <- ZIO.debug("region: " + config.region) + } yield () +} diff --git a/examples/shared/src/main/scala/zio/config/examples/configsources/XmlConfigReaderExample.scala b/examples/shared/src/main/scala/zio/config/examples/configsources/XmlConfigReaderExample.scala new file mode 100644 index 000000000..ffcae349c --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/configsources/XmlConfigReaderExample.scala @@ -0,0 +1,42 @@ +package zio.config.examples.configsources + +import zio.config._ +import zio.config.yaml._ +import zio.{Config, _} + +object XmlConfigReaderExample extends ZIOAppDefault { + + final case class Aws(region: String, account: String) + + object Aws { + val config: Config[Aws] = Config.string("region").zip(Config.string("account")).to[Aws] + } + + final case class Database(port: Int, url: String) + + object Database { + val config: Config[Database] = Config.int("port").zip(Config.string("url")).to[Database] + } + + final case class Configuration(aws: Aws, database: Database) + + object Configuration { + val config: Config[Configuration] = + Aws.config.nested("aws").zip(Database.config.nested("database")).to[Configuration].nested("config") + } + + override val bootstrap = + Runtime.setConfigProvider( + ConfigProvider.fromYamlReader(scala.io.Source.fromResource("application.xml").reader()) + ) + + def run = + for { + config <- ZIO.config(Configuration.config) + _ <- ZIO.debug("aws region: " + config.aws.region) + _ <- ZIO.debug("aws account: " + config.aws.account) + _ <- ZIO.debug("db port: " + config.database.port) + _ <- ZIO.debug("db url: " + config.database.url) + } yield () + +} diff --git a/examples/shared/src/main/scala/zio/config/examples/configsources/YamlConfigReaderExample.scala b/examples/shared/src/main/scala/zio/config/examples/configsources/YamlConfigReaderExample.scala new file mode 100644 index 000000000..49dd08c78 --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/configsources/YamlConfigReaderExample.scala @@ -0,0 +1,34 @@ +package zio.config.examples + +import zio._ +import zio.config.magnolia.deriveConfig +import zio.config.yaml._ + +import scala.io.Source + +case class KafkaClients(bootstrapServers: List[String], port: Int, region: String) + +object KafkaClients { + implicit val config: Config[KafkaClients] = deriveConfig[KafkaClients] +} + +case class ApplicationConfig(kafkaClients: KafkaClients) +object ApplicationConfig { + implicit val config: Config[ApplicationConfig] = deriveConfig[ApplicationConfig] +} + +object YamlConfigReaderExample extends ZIOAppDefault { + + override val bootstrap = + Runtime.setConfigProvider( + ConfigProvider.fromYamlReader(Source.fromResource("application.yml").reader()) + ) + + def run = for { + config <- ZIO.config(ApplicationConfig.config) + _ <- ZIO.debug("bootstrapServers: " + config.kafkaClients.bootstrapServers) + _ <- ZIO.debug("region: " + config.kafkaClients.region) + _ <- ZIO.debug("port: " + config.kafkaClients.port) + } yield () + +} diff --git a/examples/shared/src/main/scala/zio/config/examples/documentation/GeneratingConfigDocumentation.scala b/examples/shared/src/main/scala/zio/config/examples/documentation/GeneratingConfigDocumentation.scala new file mode 100644 index 000000000..6c80e169a --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/documentation/GeneratingConfigDocumentation.scala @@ -0,0 +1,19 @@ +package zio.config.examples.documentation + +import zio._ +import zio.config._ + +case class MyConfig(ldap: String, port: Int, dburl: String) +object MyConfig { + val config = { + Config.string("LDAP") ?? "Related to auth" zip + Config.int("PORT") ?? "Database port" zip + Config.string("DB_URL") ?? "URL of database" + }.to[MyConfig] +} + +object GeneratingConfigDocumentation extends ZIOAppDefault { + def run = + ZIO.debug("auto-generated documentation of MyConfig:") *> + ZIO.debug(generateDocs(MyConfig.config).toTable.toGithubFlavouredMarkdown) +} diff --git a/examples/shared/src/main/scala/zio/config/examples/documentation/NestedConfigDocumentation.scala b/examples/shared/src/main/scala/zio/config/examples/documentation/NestedConfigDocumentation.scala new file mode 100644 index 000000000..0fb35ae3f --- /dev/null +++ b/examples/shared/src/main/scala/zio/config/examples/documentation/NestedConfigDocumentation.scala @@ -0,0 +1,33 @@ +package zio.config.examples.documentation + +import zio._ +import zio.config._ + +final case class Credentials(user: String, password: String) +object Credentials { + val config: Config[Credentials] = { + Config.string("USERNAME") ?? "Example: ZioUser" zip + Config.string("PASSWORD") ?? "Example: ZioPass" + }.to[Credentials] ?? "Credentials" +} +final case class Database(port: Int, url: String) +object Database { + val config = { + Config.int("PORT") ?? "Example: 8088" zip Config.string("URL") ?? "Example: abc.com" + }.to[Database] ?? "Database" +} +final case class AppConfig(secret: Option[String], credentials: Credentials, database: Database) + +object AppConfig { + def config: Config[AppConfig] = { + Config.string("SECRET").optional ?? ("Application secret") zip + Credentials.config.nested("CREDENTIALS") zip + Database.config.nested("DATABASE") + }.to[AppConfig] +} + +object NestedConfigDocumentation extends ZIOAppDefault { + def run = + ZIO.debug("Auto-generated documentation of AppConfig:") *> + ZIO.debug(generateDocs(AppConfig.config).toTable.toGithubFlavouredMarkdown) +} diff --git a/zio-config-docs/src/main/scala/utils.scala b/zio-config-docs/src/main/scala/utils.scala new file mode 100644 index 000000000..49565bfca --- /dev/null +++ b/zio-config-docs/src/main/scala/utils.scala @@ -0,0 +1,52 @@ +import scala.io.Source + +object utils { + + def readSource(path: String, lines: Seq[(Int, Int)]): String = { + def readFile(path: String) = + try { + Source.fromFile("../" + path) + } catch { + case _ => Source.fromFile(path) + } + + if (lines.isEmpty) { + val content = readFile(path).getLines().mkString("\n") + content + } else { + val chunks = for { + (from, to) <- lines + } yield readFile(path) + .getLines() + .toArray[String] + .slice(from - 1, to) + .mkString("\n") + + chunks.mkString("\n\n") + } + } + + def fileExtension(path: String): String = { + val javaPath = java.nio.file.Paths.get(path) + val fileExtension = + javaPath.getFileName.toString + .split('.') + .lastOption + .getOrElse("") + fileExtension + } + + def printSource( + path: String, + lines: Seq[(Int, Int)] = Seq.empty, + comment: Boolean = true, + showLineNumbers: Boolean = false, + ) = { + val title = if (comment) s"""title="$path"""" else "" + val showLines = if (showLineNumbers) "showLineNumbers" else "" + println(s"""```${fileExtension(path)} ${title} ${showLines}""") + println(readSource(path, lines)) + println("```") + } + +}