ZIO Config

repository·series/4.x·Indexed 42 days ago

https://github.com/zio/zio-config

A ZIO-based library that extends ZIO's core Config language to read, validate, and derive configurations from sources including HOCON, JSON, YAML, TOML, and XML. It features automatic derivation via Magnolia, type-level constraints using Refined types, descriptive error accumulation, and tools to auto-generate configuration documentation in Markdown for GitHub and Confluence.

Tokens
15.1K
Snippets
53
Records
60
Agent score
53%

What's inside ZIO Config

  1. Overview of ZIO Config features

    series/4.x

    ZIO Config is an extension to the core ZIO Config language. Key capabilities include:

    • Support for Various Sources: Read flat or nested configurations from HOCON, JSON, YAML, TOML, XML, maps, and more using IndexedFlat.
    • Automatic Derivation: Built-in support for automatically deriving readers and writers for case classes and sealed traits.
    • Type-level Constraints and Validation: Support for Refined types allows for type-level predicates to constrain configuration values.
    • Descriptive Errors: Instead of failing fast, the library accumulates and reports all configuration errors to the user.
    • Automatic Documentation: Capability to auto-generate documentation for your configurations.
  2. Handle and visualize configuration errors

    series/4.x

    When configuration loading fails, ZIO Config returns a ReadError. This error type is designed to accumulate multiple errors using AndErrors (parallel errors) and OrErrors (sequential/alternative errors) semantics.

    Instead of simple error messages, use ReadError.prettyPrint to display a structured, visual representation of the error path and the specific details of all collected misconfigurations.

  3. Handle Indexed Sequences in Flat Maps

    series/4.x

    When using ConfigProvider.fromMap to represent sequences (like Lists) in a flat structure, you can use dot notation with indices (e.g., employees[0].name).

    To represent an empty list in a flat structure, it is recommended to use the special token <nil> rather than an empty string "" to avoid ambiguity.

    import zio.config._, magnolia._
    
    final case class Department(name: String, block: Int)
    final case class Employee(departments: List[Department], name: String)
    final case class Config(employees: List[Employee])
    
    val map = Map(
      "employees[0].name" -> "jon",
      "employees[0].departments[0].name" -> "science",
      "employees[0].departments[0].block" -> "10",
      "employees[1].name" -> "foo",
      "employees[1].departments" -> "<nil>" // Represents an empty list
    )
    
    ConfigProvider.fromMap(map).load(deriveConfig[Config])
  4. Use Scalaz/Cats typeclasses with Config

    series/4.x

    For highly polymorphic code, zio-config provides instances for Config that allow it to participate in typeclass-based operations (like Applicative or Monoid operations). This allows you to combine multiple Config objects using standard functional programming patterns instead of manually zipping them.

    Additionally, you can load Scalaz-specific data types like Maybe using the maybe combinator provided by the zio-config-scalaz module.

    // Combining Configs using Scalaz Applicative
    import _root_.scalaz._, Scalaz._
    import zio.config.scalaz.instances._
    
    // Example of an addition function using Applicative
    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)))
        
    // Config can now be used with the 'add' function
    val configResult = add(Config.int("marks1"), Config.int("marks2"))
    ConfigProvider.fromMap(Map("marks1" -> "10", "marks2" -> "20")).load(configResult) // returns 30
    
    // Loading Scalaz-specific types
    import zio.config.scalaz._
    import _root_.scalaz.Maybe
    
    val config: Config[Maybe[Int]] = maybe(Config.int("age"))
  5. Load Enumeratum enums with zio-config

    series/4.x

    You can directly load enumeratum enums using zio-config without relying on manual auto-derivation. By importing zio.config.enumeratum._, you can use the `enum`(EnumType) combinator to map configuration values to your Enumeratum enum entries. This is useful when your configuration contains string representations of enum values.

    // Define your Enumeratum enum
    sealed trait Greeting extends EnumEntry
    object Greeting extends Enum[Greeting] {
      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
  6. Handle external types with custom `DeriveConfig` instances

    series/4.x

    Automatic derivation requires every field in a case class to have an instance of DeriveConfig. If you are using a type from an external library (e.g., an AWS SDK type) that does not have a DeriveConfig instance, you must provide one manually.

    Best Practices for placement:

    • Owned types: Place the implicit DeriveConfig instance in the companion object of the type.
    • External types: Place the implicit instance closer to where the automatic derivation is called (e.g., in the local scope or a configuration module).
    import zio.config.magnolia._
    
    // Example: Providing a custom descriptor for an external type
    // Assume AwsRegion is from an external library
    implicit val awsRegionConfig: DeriveConfig[Aws.Region] =
      DeriveConfig[String].map(string => AwsRegion.from(string))
    
    // Example: Providing a descriptor for a value class in its companion object
    final case class AwsRegion(value: String) extends AnyVal
    
    object AwsRegion {
      implicit val descriptor: DeriveConfig[AwsRegion] = 
        DeriveConfig[String].map(AwsRegion(_))
    }
  7. Add configuration source modules

    series/4.x

    ZIO Config uses separate modules for different configuration formats. Add the specific module required for your source type to your build.sbt file:

    • HOCON / JSON: zio-config-typesafe
    • YAML: zio-config-yaml
    • TOML: zio-config-toml
    • XML (experimental): zio-config-xml
    // HOCON / JSON
    libraryDependencies += "dev.zio" %% "zio-config-typesafe" % "<version>"
    
    // YAML
    libraryDependencies += "dev.zio" %% "zio-config-yaml" % "<version>"
    
    // TOML
    libraryDependencies += "dev.zio" %% "zio-config-toml" % "<version>"
    
    // XML (experimental)
    libraryDependencies += "dev.zio" %% "zio-config-xml" % "<version>"
  8. Use automatic validations with zio-config-refined

    series/4.x

    By including the zio-config-refined module, you can integrate the Refined library into your configuration schema. This allows you to enforce constraints (like non-empty strings or numeric ranges) directly on your configuration parameters with high ergonomics.

    To use these features, import the zio.config._ and zio.config.refined._ packages.

    import zio.Config
    import zio.ConfigProvider
    import zio.config._, refined._
  9. Automatically derive configuration with `deriveConfig`

    series/4.x

    By using zio-config-magnolia, you can avoid boilerplate by automatically deriving Config instances for your case classes and sealed traits. To use this, import zio.config.magnolia._ and call deriveConfig[T].

    By default, field names and class names in the configuration source match the names used in your Scala code (e.g., case class field names and sealed trait case object names).

    import zio._
    import zio.config._
    import zio.config.typesafe._
    import zio.config.magnolia._
    
    case class MyConfig(x: String)
    
    val source = ConfigProvider.fromHoconString("x = hello")
    source.load(deriveConfig[MyConfig])
    // res: Right(MyConfig("hello"))
  10. Add support for various configuration sources

    series/4.x

    ZIO Config is modular. To read from specific file formats, you must add the corresponding module to your build.sbt dependencies.

    Available source modules:

    • HOCON / JSON: zio-config-typesafe
    • YAML: zio-config-yaml
    • TOML: zio-config-toml
    • XML (experimental): zio-config-xml
    // HOCON / JSON
    libraryDependencies += "dev.zio" %% "zio-config-typesafe" % "<version>"
    
    // YAML
    libraryDependencies += "dev.zio" %% "zio-config-yaml" % "<version>"
    
    // TOML
    libraryDependencies += "dev.zio" %% "zio-config-toml" % "<version>"
    
    // XML (experimental)
    libraryDependencies += "dev.zio" %% "zio-config-xml" % "<version>"
  11. Quick Start setup

    series/4.x

    For a quick start that includes automatic derivation (via Magnolia), HOCON/JSON support, and Refined type support, add the following dependencies to your build.sbt:

    libraryDependencies += "dev.zio" %% "zio-config"          % "<version>"
    libraryDependencies += "dev.zio" %% "zio-config-magnolia" % "<version>"
    libraryDependencies += "dev.zio" %% "zio-config-typesafe" % "<version>"
    libraryDependencies += "dev.zio" %% "zio-config-refined"  % "<version>"