scalaziozio-config

How to handle an ADT (sealed trait) with ZIO config


How can I add a Configuration Description manually for an Algebraic Data Type with ZIO Conf.

In the examples I found an example on how to handle ADTs with Magnolia.

Is this also possible when adding manually a Description of the Configuration?

Here an example:

sealed trait Dance
final case class A(any: Person)   extends Dance
final case class B(body: Height)  extends Dance

final case class Person(name: String, age: Option[Int])
final case class Height(height: Long)

With Magnolia:

val danceConfig = description[Dance]

Manually:

val danceConfig = ???

Solution

  • It is verbose as you would expect. But there are different ways of doing it, and it's a matter of preference.

    We tried to be bit more verbose than required in both these options for better understanding

    Option 1:

      val personConfig =
        (string("name") |@| int("age").optional)(Person.apply, Person.unapply)
    
      val heightConfig =
        long("height").xmap(Height)(_.height)
    
      val aConfig = nested("any")(personConfig).xmap(A)(_.any)
      val bConfig = nested("body")(heightConfig).xmap(B)(_.body)
      val cConfig = boolean("can").xmap(C)(_.can)
      val dConfig = string("dance").xmap(D)(_.dance)
    
      val danceConfig =
        aConfig
          .orElseEither(bConfig)
          .orElseEither(cConfig)
          .orElseEither(dConfig)
          .xmap({
            case Right(value) => value: Dance
            case Left(value) =>
              value match {
                case Right(value) => value: Dance
                case Left(value) =>
                  value match {
                    case Right(value) => value: Dance
                    case Left(value)  => value: Dance
                  }
              }
          })({
                case d @ D(_) => Right(d)
                case c @ C(_) => Left(Right(c))
                case b @ B(_) => Left(Left(Right(b)))
                case a @ A(_) => Left(Left(Left(a)))
              }
          )
    
    

    A bit convoluted during the write side, but it is all type driven.

    Option 2

       val personConfig =
        (string("name") |@| int("age").optional)(Person.apply, Person.unapply)
    
      val heightConfig =
        long("height").xmap(Height)(_.height)
    
      val aConfig = nested("any")(personConfig).xmap(A)(_.any)
      val bConfig = nested("body")(heightConfig).xmap(B)(_.body)
      val cConfig = boolean("can").xmap(C)(_.can)
      val dConfig = string("dance").xmap(D)(_.dance)
    
      val aConfigAsDance =
        aConfig.xmapEither(a => Right(a: Dance))({
          case a: A => Right(a)
          case _    => Left("unable to write back") 
        })
    
      val bConfigAsDance =
        bConfig.xmapEither(a => Right(a: Dance))({
          case a: B => Right(a)
          case _    => Left("unsable to write back")
        })
    
      val cConfigAsDance =
        cConfig.xmapEither(a => Right(a: Dance))({
          case a: C => Right(a)
          case _    => Left("unsable to write back")
        })
    
      val dConigAsDance =
        dConfig.xmapEither(a => Right(a: Dance))({
          case a: D => Right(a)
          case _    => Left("unsable to write back")
        })
    
      val danceConfig =
        aConfigAsDance.orElse(bConfigAsDance).orElse(cConfigAsDance).orElse(dConigAsDance)
    
    
    

    You would already note, during the write part (second arg to xmapEither) we make sure it is the correct type. Example: In aConfigAsDance, it is unsafe to assume it can only be A and do an asInstanceOf.

    With xmapeither we are able to write safe and pure code and we followed it.

    In future, zio-config will come up with some helper functions to deal with Either. This is because ZIO-Config philosophy is to provide as less magical interface to the user as possible, while you can still use zio-config-magnolia to shorten them to just one line, which is

    val danceConfig = description[Dance]
    

    Good to have this example back in zio-config if you are interested. Thanks a lot for this question, and hope the answer is helpful.