servant-generate-0.1: src/Servant/Server/Generate.hs
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Servant.Server.Generate
( -- * Using this library
-- $using
-- * The 'GenerateServer' class
GenerateServer(..)
, -- * Utilities
unconstrained, constrained
, handlers
, Flatten, NonEmpty
) where
import Data.Proxy
import GHC.Exts
import GHC.TypeLits
import Servant.API
import Servant.API.Internal.Test.ComprehensiveAPI
import Servant.Server
-- | 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!).
class GenerateServer (api :: *) (constraints :: [* -> Constraint]) where
-- | 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.
generateServer :: forall (m :: * -> *).
Proxy api
-> Proxy constraints
-> Proxy m
-> (forall a. Flatten constraints a => Proxy a -> m a)
-> ServerT api m
instance (GenerateServer a c, GenerateServer b c) => GenerateServer (a :<|> b) c where
generateServer _ c m f = generateServer (Proxy @ a) c m f
:<|> generateServer (Proxy @ b) c m f
instance (KnownSymbol path, GenerateServer api c) => GenerateServer (path :> api) c where
generateServer _ c m f = generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (Summary s :> api) c where
generateServer _ c m f = generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (Description s :> api) c where
generateServer _ c m f = generateServer (Proxy @ api) c m f
instance GenerateServer EmptyAPI c where
generateServer _ _ _ _ = emptyServer
instance (KnownSymbol s, GenerateServer api c) => GenerateServer (Capture' mods s a :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance (KnownSymbol s, GenerateServer api c) => GenerateServer (CaptureAll s a :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (ReqBody' mods cts a :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (RemoteHost :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (IsSecure :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (Vault :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (HttpVersion :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance (KnownSymbol s, GenerateServer api c) => GenerateServer (QueryParam' mods s a :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance (KnownSymbol s, GenerateServer api c) => GenerateServer (QueryFlag s :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance (KnownSymbol s, GenerateServer api c) => GenerateServer (QueryParams s a :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance (KnownSymbol h, GenerateServer api c) => GenerateServer (Header' mods h a :> api) c where
generateServer _ c m f = \_ -> generateServer (Proxy @ api) c m f
instance GenerateServer api c => GenerateServer (WithNamedContext name subContext api) c where
generateServer _ c m f = generateServer (Proxy @ api) c m f
-- | Requires all constraints in @cs@ to be satisfied by @a@.
instance (KnownNat status, Flatten cs a) => GenerateServer (Verb (method :: StdMethod) (status :: Nat) (cts :: [*]) (a :: *)) cs where
generateServer _ _ _ f = f (Proxy @ a)
type family Flatten (cs :: [* -> Constraint]) (a :: *) :: Constraint where
Flatten '[] a = ()
Flatten (c ': cs) a = (c a, Flatten cs a)
-- * Utilities for specifying the arguments to 'generateServer'
-- | 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.
unconstrained :: Proxy ('[] :: [* -> Constraint])
unconstrained = Proxy
type family NonEmpty (cs :: [* -> Constraint]) :: Constraint where
NonEmpty '[] = TypeError ('Text "empty list of constraints used with 'constrained'")
NonEmpty cs = ()
-- | 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]@.
constrained
:: forall (cs :: [* -> Constraint]). NonEmpty cs => Proxy cs
constrained = Proxy
-- | 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'@.
handlers :: forall (m :: * -> *). Proxy m
handlers = Proxy
-- do we have all instances?
class A a where
getA :: Proxy a -> a
instance A NoContent where
getA _ = NoContent
instance A Int where
getA _ = 0
instance A a => A (Headers '[] a) where
getA _ = Headers (getA (Proxy :: Proxy a)) HNil
instance (A t, A (Headers hs a))
=> A (Headers (Header h t ': hs) a) where
getA _ = case getA (Proxy @ (Headers hs a)) of
Headers a hs -> Headers a $
(Header $ getA (Proxy @ t)) `HCons` hs
s :: Server ComprehensiveAPIWithoutRaw
s = generateServer
(Proxy @ ComprehensiveAPIWithoutRaw)
(constrained @ '[A])
(handlers @ Handler)
(return . getA)
-- $using
-- 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)