servant-generate-0.1: Utilities for generating mock server implementations

Safe HaskellNone
LanguageHaskell2010

Servant.Server.Generate

Contents

Synopsis

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 MonadError ServantErr, for example.

-- 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

generateServer

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 Proxy to) any response type a that implements the aforementionned constraints, can produce a value of that type in m,

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

GenerateServer EmptyAPI c Source # 

Methods

generateServer :: Proxy * EmptyAPI -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * EmptyAPI m Source #

(GenerateServer a c, GenerateServer b c) => GenerateServer ((:<|>) a b) c Source # 

Methods

generateServer :: Proxy * (a :<|> b) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall d. Flatten c d => Proxy * d -> m d) -> ServerT * (a :<|> b) m Source #

GenerateServer api c => GenerateServer (WithNamedContext name subContext api) c Source # 

Methods

generateServer :: Proxy * (WithNamedContext name subContext api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * (WithNamedContext name subContext api) m Source #

GenerateServer api c => GenerateServer ((:>) * HttpVersion api) c Source # 

Methods

generateServer :: Proxy * ((* :> HttpVersion) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((* :> HttpVersion) api) m Source #

GenerateServer api c => GenerateServer ((:>) * (ReqBody' mods cts a) api) c Source # 

Methods

generateServer :: Proxy * ((* :> ReqBody' mods cts a) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall b. Flatten c b => Proxy * b -> m b) -> ServerT * ((* :> ReqBody' mods cts a) api) m Source #

GenerateServer api c => GenerateServer ((:>) * RemoteHost api) c Source # 

Methods

generateServer :: Proxy * ((* :> RemoteHost) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((* :> RemoteHost) api) m Source #

(KnownSymbol s, GenerateServer api c) => GenerateServer ((:>) * (QueryParam' mods s a) api) c Source # 

Methods

generateServer :: Proxy * ((* :> QueryParam' mods s a) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall b. Flatten c b => Proxy * b -> m b) -> ServerT * ((* :> QueryParam' mods s a) api) m Source #

(KnownSymbol s, GenerateServer api c) => GenerateServer ((:>) * (QueryParams s a) api) c Source # 

Methods

generateServer :: Proxy * ((* :> QueryParams s a) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall b. Flatten c b => Proxy * b -> m b) -> ServerT * ((* :> QueryParams s a) api) m Source #

(KnownSymbol s, GenerateServer api c) => GenerateServer ((:>) * (QueryFlag s) api) c Source # 

Methods

generateServer :: Proxy * ((* :> QueryFlag s) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((* :> QueryFlag s) api) m Source #

(KnownSymbol h, GenerateServer api c) => GenerateServer ((:>) * (Header' * mods h a) api) c Source # 

Methods

generateServer :: Proxy * ((* :> Header' * mods h a) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall b. Flatten c b => Proxy * b -> m b) -> ServerT * ((* :> Header' * mods h a) api) m Source #

GenerateServer api c => GenerateServer ((:>) * IsSecure api) c Source # 

Methods

generateServer :: Proxy * ((* :> IsSecure) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((* :> IsSecure) api) m Source #

GenerateServer api c => GenerateServer ((:>) * (Summary s) api) c Source # 

Methods

generateServer :: Proxy * ((* :> Summary s) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((* :> Summary s) api) m Source #

GenerateServer api c => GenerateServer ((:>) * (Description s) api) c Source # 

Methods

generateServer :: Proxy * ((* :> Description s) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((* :> Description s) api) m Source #

(KnownSymbol s, GenerateServer api c) => GenerateServer ((:>) * (Capture' mods s a) api) c Source # 

Methods

generateServer :: Proxy * ((* :> Capture' mods s a) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall b. Flatten c b => Proxy * b -> m b) -> ServerT * ((* :> Capture' mods s a) api) m Source #

(KnownSymbol s, GenerateServer api c) => GenerateServer ((:>) * (CaptureAll s a) api) c Source # 

Methods

generateServer :: Proxy * ((* :> CaptureAll s a) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall b. Flatten c b => Proxy * b -> m b) -> ServerT * ((* :> CaptureAll s a) api) m Source #

GenerateServer api c => GenerateServer ((:>) * Vault api) c Source # 

Methods

generateServer :: Proxy * ((* :> Vault) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((* :> Vault) api) m Source #

(KnownSymbol path, GenerateServer api c) => GenerateServer ((:>) Symbol path api) c Source # 

Methods

generateServer :: Proxy * ((Symbol :> path) api) -> Proxy [* -> Constraint] c -> Proxy (* -> *) m -> (forall a. Flatten c a => Proxy * a -> m a) -> ServerT * ((Symbol :> path) api) m Source #

(KnownNat status, Flatten cs a) => GenerateServer (Verb StdMethod method status cts a) cs Source #

Requires all constraints in cs to be satisfied by a.

Methods

generateServer :: Proxy * (Verb StdMethod method status cts a) -> Proxy [* -> Constraint] cs -> Proxy (* -> *) m -> (forall b. Flatten cs b => Proxy * b -> m b) -> ServerT * (Verb StdMethod method status cts a) m Source #

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 #

Equations

Flatten '[] a = () 
Flatten (c ': cs) a = (c a, Flatten cs a) 

type family NonEmpty (cs :: [* -> Constraint]) :: Constraint where ... Source #

Equations

NonEmpty '[] = TypeError (Text "empty list of constraints used with 'constrained'") 
NonEmpty cs = ()