{-# 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)