Safe Haskell | None |
---|---|
Language | Haskell2010 |
- class GenerateServer (api :: *) (constraints :: [* -> Constraint]) where
- unconstrained :: Proxy ('[] :: [* -> Constraint])
- constrained :: forall (cs :: [* -> Constraint]). NonEmpty cs => Proxy cs
- handlers :: forall (m :: * -> *). Proxy m
- type family Flatten (cs :: [* -> Constraint]) (a :: *) :: Constraint where ...
- type family NonEmpty (cs :: [* -> Constraint]) :: Constraint where ...
Using this library
Let's imagine we're working with the following simple API and we would like to spin up a "silly" server implementation that returns responses of the right type (or errors out) for each endpoint.
type API = Get '[JSON] () :<|> "int" :> Get '[JSON] Int api :: Proxy API api = Proxy
First, let's see how we can get a server implementation where all the handlers throw a 404 error.
-- x :: Handler () :<|> Handler Int -- inferred just fine x = generateServer api unconstrained (handlers @ Handler) (\_ -> throwError err404)
We could more generally take the monad as argument, simply
requiring that it is a
, for
example.MonadError
ServantErr
-- x' :: MonadError ServantErr m => Proxy m -> m () :<|> m Int -- inferred just fine x' mon = generateServer api unconstrained mon (\_ -> throwError err404)
Now, let's see an example where we need one constraint to be satisfied for all response types present in the API.
-- we will require all response types to provide -- an instance of this typeclass. class Default a where def :: Proxy a -> a instance Default () where def _ = () instance Default Int where def _ = 0 -- y :: forall m. Applicative m => Proxy m -> m () :<|> m Int -- inferred just fine y mon = generateServer api (constrained @ '[Default]) mon $ \pa -> pure (def pa)
Finally, let's see an example where we require all response types of an API to have instances for several type classes.
-- we will require instances of Tweak in addition to -- Default. class Tweak a where tweak :: a -> a instance Tweak () where tweak () = () instance Tweak Int where tweak n = n^2 -- z :: forall m. Monad m => Proxy m -> m () :<|> m Int z mon = generateServer api (constrained @ '[Default, Tweak]) mon $ \pa -> return $ tweak (def pa) -- instantiated at a particular monad, say Handler: z' = z (handlers @ Handler)
The GenerateServer
class
class GenerateServer (api :: *) (constraints :: [* -> Constraint]) where Source #
A class that'll define the generated server using the supplied
constraints and some function that takes a response type (through a Proxy
)
and returns a computation in an arbitrary monad that returns a value of
the aforementionned response type.
What does this all mean?
Well, for example, we can use an empty list of
constraints (see unconstrained
) and just systematically call throwError
in all endpoints. The monad in that case would be Handler
, and the result of
calling generateServer
on any API type would be a server implementation for that
API that just errors out in each handler.
Another example would be to use a constraint list of just one class (with
constrained
), say Arbitrary
, and pass a function to generateServer
that
simply generates a random value in IO (something like
_ -> liftIO $ generate (arbitrary :: Gen a)
). The result of calling this on a
given API type would be a server implementation that can live in any `MonadIO m`,
where for each endpoint we simply generate a random response (of the right type!).
generateServer :: forall (m :: * -> *). Proxy api -> Proxy constraints -> Proxy m -> (forall a. Flatten constraints a => Proxy a -> m a) -> ServerT api m Source #
Given:
- a particular API type
api
, - some constraint(s) that all response types in the API must satisfy,
- a "monad-like" type constructor
m
, of kind* -> *
, - a function that, when given (a
Proxy
to) any response typea
that implements the aforementionned constraints, can produce a value of that type inm
,
generateServer
gives you back an implementation of the given API
in with handlers returning responses in m
, that is, a value of type
.ServerT
api m
When m
is just Handler
, you can directly serve
that implementation.
If you're working with another monad, you need to use hoistServer
on the
result of generateServer
before you can serve
it.
Utilities
unconstrained :: Proxy ('[] :: [* -> Constraint]) Source #
Meant to be used as the second argument to generateServer
when your
implementation function doesn't need any typeclass constraint and can
just work with any response type.
constrained :: forall (cs :: [* -> Constraint]). NonEmpty cs => Proxy cs Source #
Meant to be used as the second argument to generateServer
when your
implementation function requires one or more constraints on the all
the response types of your API, e.g
.constrained
@ '[Show, Random, Monoid]
handlers :: forall (m :: * -> *). Proxy m Source #
Meant to be used as the third argument to generateServer
. It's a simple
more readable wrapper around Proxy
. Use a type application to specify
the monad, e.g
.handlers
@ AppMonad
type family Flatten (cs :: [* -> Constraint]) (a :: *) :: Constraint where ... Source #
type family NonEmpty (cs :: [* -> Constraint]) :: Constraint where ... Source #