guardrail-dev / guardrail-dev/guardrail
Extensible enums
- Dominant language
- Scala
- Stars
- 541
- Forks
- 138
- PR merge metrics
- No merged PRs in 30d
Description
For interoperation with poorly specced or flexible downstream APIs, clients should be able to represent a branch of an enumeration that isn't known. This permits accepting unknown branches and doing work with them without rejecting the response from client calls (currently, enumeration lookup failures result in a decoder failure for clients).
Currently, generated enumerations look like this:
```scala
sealed abstract class Foo(val value: String) { override def toString: String = value.toString }
object Foo {
object members {
case object Bar extends Foo("Bar")
}
val Bar: Foo = members.Bar
val values = Vector(Bar)
def parse(value: String): Option[Foo] = values.find(_.value == value)
implicit val encodeFoo: Encoder[Foo] = Encoder[String].contramap(_.value)
implicit val decodeFoo: Decoder[Foo] = Decoder[String].emap(value => parse(value).toRight(s"$value not a member of Foo"))
implicit val addPathFoo: AddPath[Foo] = AddPath.build(_.value)
implicit val showFoo: Show[Foo] = Show.build(_.value)
}
```
A possible alteration to accept unknown members (currently unsure if this should be opt-in or opt-out, definitely needs more thought):
```diff
@@ -2,10 +2,12 @@
object Foo {
object members {
case object Bar extends Foo("Bar")
+ class Unknown private[Foo] (value: String) extends Foo(value)
}
val Bar: Foo = members.Bar
+ def Unknown(value: String): Foo = new members.Unknown(value)
val values = Vector(Bar)
- def parse(value: String): Option[Foo] = values.find(_.value == value)
+ def parse(value: String): Option[Foo] = values.find(_.value == value).orElse(new members.Unknown(value))
implicit val encodeFoo: Encoder[Foo] = Encoder[String].contramap(_.value)
implicit val decodeFoo: Decoder[Foo] = Decoder[String].emap(value => parse(value).toRight(s"$value not a member of Foo"))
implicit val addPathFoo: AddPath[Foo] = AddPath.build(_.value)
```
Initially I thought the `Unknown` branch should not expose a constructor, as it would prevent invalid data from being supplied by the clients into downstream services. This has the unfortunate side-effect of not allowing services to round-trip to persistence layers; consider receiving values of an array via client API call, writing those values into a database, then attempting to use those values to make future API calls.
Contributor guide
Assessment
This issue has not been assessed yet.