| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Servant.Server.Generate
- 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 = ProxyFirst, 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!).
Minimal complete definition
Methods
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
Proxyto) any response typeathat 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.
Instances
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 #