Schema for `Nothing`
I have the following generic model of responses from a server API:
sealed trait Status[+E, +A]
final case class Error[E](error: E) extends Status[E, Nothing]
final case class Fatal(reason: String) extends Status[Nothing, Nothing]
final case class Success[A](a: A) extends Status[Nothing, A]
final case class Timeout() extends Status[Nothing, Nothing]
implicit def statusSchema[A, E](implicit es: Schema[E], as: Schema[A]): Schema[Status[E, A]] =
Schema.derived
It works fine if an API method returns something like Status[String, Unit]. But what if some method demoMethod is known not to respond with Error case? It could be encoded as Status[Nothing, Unit]:
val demoMethod: PublicEndpoint[Args, Unit, Status[Nothing, Unit], Any] =
basepoint
.post
.in("demo-method")
.in(jsonBody[Args])
.out(jsonBody[Status[Nothing, Unit]])
For this to work we need a shema for Nothing, in other words the absence of a value.
Currently I use Schema.any as a workaround:
implicit val nothingSchema: Schema[Nothing] =
Schema.any
But of course any has not the same meaning as we want.
I think Schema.any is as good a choice as anything else. Question is, how should it end up being represented in OpenAPI?
I see two possible approaches:
-
Hard, but preferrable. Extending deriving algorithm to exclude from derived schema all products with one or more parameters of type
Nothing. For example,Schema.derived[Status[String,Unit]]derives something like (pseudo-code for brievety):Schema(SCoproduct( List( Schema(SProduct("Error", /* ... */)), Schema(SProduct("Fatal", /* ... */)), Schema(SProduct("Success", /* ... */)), Schema(SProduct("Timeout", /* ... */)) ))But
Schema.derived[Status[Nothing,Unit]]could detect that the parametererrorof subtypeErrorhas the typeNothingso that it should be excluded:Schema(SCoproduct( List( Schema(SProduct("Fatal", /* ... */)), Schema(SProduct("Success", /* ... */)), Schema(SProduct("Timeout", /* ... */)) ))Not sure is that possible with Scala 2/3 macros.
-
Easy, but less ergonomic for end-users. Since "JSON Schema vocabularies are free to define their own extended type system", we can explicitly add the type
nothing. Yes, it requires some training of readers of generated OpenAPI spec, but it clearly reveals intention behind a shema design.
As for 2., I'm not sure if that would be of any use, as OpenAPI is valuable only as long as the readers know what this means.
- is more promising, though. Probably not fully-automatic (as there might be cases when the
Nothingtype parameter appears in a class that might be instantiated), and outside of a macro, but wouldn't it be possible to write a functionSchema => Schema, which looks for fields where the type isNothing, and filters out their containing objects? Something like aeliminateUninstantiable