MultiVerb produces an endpoint which can return multiple values with various content types and status codes. It is similar to UVerb and behaves similarly, but it has some important differences:
Descriptions and statuses can be attached to individual responses without using wrapper types and without affecting the handler return type.
The return type of the handler can be decoupled from the types of the individual responses. One can use a Union type just like for UVerb, but MultiVerb also supports using an arbitrary type with an AsUnion instance. Each response is responsible for their content type.
Headers can be attached to individual responses, also without affecting the handler return type.
Example
Let us create an endpoint that captures an Int and has the following logic:
If the number is negative, we return status code 400 and an empty body;
If the number is even, we return a Bool in the response body;
If the number is odd, we return another Int in the response body.
import qualified Generics.SOP as GSOP-- All possible HTTP responses
type Responses =
'[ type RespondEmpty 400 "Negative"
, type Respond 200 "Even number" Bool
, type Respond 200 "Odd number" Int
]
-- All possible return types
data Result
= NegativeNumber
| Odd Int
| Even Bool
deriving stock (Generic)
deriving (AsUnion Responses)
via GenericAsUnion Responses Result
instance GSOP.Generic ResultThese deriving statements above tie together the responses and the return values, and the order in which they are defined matters. For instance, if Even and Odd had switched places in the definition of Result, this would provoke an error:
• No instance for ‘AsConstructor
((:) @Type Int ('[] @Type)) (Respond 200 "Even number" Bool)’
arising from the 'deriving' clause of a data type declarationIf you would prefer to write an intance of AsUnion by yourself, read more in the typeclass' documentation.
Finally, let us write our endpoint description:
type MultipleChoicesInt =
Capture "int" Int
:> MultiVerb
'GET
'[JSON]
Responses
Result