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 = ???
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.