servant/servant-server/src/Servant/Server/Internal.hs

811 lines
33 KiB
Haskell
Raw Normal View History

{-# LANGUAGE CPP #-}
2016-01-14 23:43:48 +01:00
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
2017-05-16 18:26:47 +02:00
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
2017-09-08 18:21:16 +02:00
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
2017-10-19 23:41:49 +02:00
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
#if MIN_VERSION_base(4,9,0) && __GLASGOW_HASKELL__ >= 802
#define HAS_TYPE_ERROR
#endif
#ifdef HAS_TYPE_ERROR
{-# LANGUAGE UndecidableInstances #-}
#endif
2015-12-27 17:54:29 +01:00
#include "overlapping-compat.h"
module Servant.Server.Internal
( module Servant.Server.Internal
, module Servant.Server.Internal.BasicAuth
, module Servant.Server.Internal.Context
2017-01-16 10:44:25 +01:00
, module Servant.Server.Internal.Handler
, module Servant.Server.Internal.Router
, module Servant.Server.Internal.RoutingApplication
, module Servant.Server.Internal.ServantErr
) where
2014-12-10 16:10:57 +01:00
import Control.Monad (join, when)
import Control.Monad.Trans (liftIO)
2017-01-18 11:25:18 +01:00
import Control.Monad.Trans.Resource (runResourceT)
import qualified Data.ByteString as B
2017-10-19 00:43:43 +02:00
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Char8 as BC8
import qualified Data.ByteString.Lazy as BL
2017-10-19 00:43:43 +02:00
import Data.Maybe (fromMaybe, mapMaybe,
isNothing, maybeToList)
import Data.Either (partitionEithers)
import Data.String (IsString (..))
import Data.String.Conversions (cs, (<>))
2017-09-08 18:21:16 +02:00
import Data.Tagged (Tagged(..), retag, untag)
2017-01-16 13:17:20 +01:00
import qualified Data.Text as T
import Data.Typeable
import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
symbolVal)
import Network.HTTP.Types hiding (Header, ResponseHeaders)
2017-10-19 00:43:43 +02:00
import qualified Network.HTTP.Media as NHM
import Network.Socket (SockAddr)
import Network.Wai (Application, Request,
httpVersion, isSecure,
lazyRequestBody,
rawQueryString, remoteHost,
requestHeaders, requestMethod,
2017-10-19 00:43:43 +02:00
responseLBS, responseStream,
vault)
2016-03-01 19:25:04 +01:00
import Prelude ()
2016-03-01 12:41:24 +01:00
import Prelude.Compat
import Web.HttpApiData (FromHttpApiData, parseHeader,
parseQueryParam,
2016-08-31 12:32:12 +02:00
parseUrlPieceMaybe,
parseUrlPieces)
import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture,
2017-05-16 11:34:07 +02:00
CaptureAll, Verb, EmptyAPI,
2016-05-26 20:10:15 +02:00
ReflectMethod(reflectMethod),
IsSecure(..), Header', QueryFlag,
QueryParam', QueryParams, Raw,
RemoteHost, ReqBody', Vault,
2017-06-08 17:27:36 +02:00
WithNamedContext,
2017-10-19 00:43:43 +02:00
Description, Summary,
Accept(..),
2017-10-19 23:41:49 +02:00
FramingRender(..), Stream,
StreamGenerator(..), ToStreamGenerator(..),
BoundaryStrategy(..),
If, SBool (..), SBoolI (..))
import Servant.API.Modifiers (unfoldRequestArgument, RequestArgument, FoldRequired, FoldLenient)
import Servant.API.ContentTypes (AcceptHeader (..),
AllCTRender (..),
AllCTUnrender (..),
AllMime,
2017-10-19 00:43:43 +02:00
MimeRender(..),
canHandleAcceptH)
import Servant.API.ResponseHeaders (GetHeaders, Headers, getHeaders,
getResponse)
2015-01-13 20:40:41 +01:00
2016-02-28 23:23:32 +01:00
import Servant.Server.Internal.Context
import Servant.Server.Internal.BasicAuth
2017-01-16 10:44:25 +01:00
import Servant.Server.Internal.Handler
import Servant.Server.Internal.Router
import Servant.Server.Internal.RoutingApplication
2015-05-02 04:38:53 +02:00
import Servant.Server.Internal.ServantErr
#ifdef HAS_TYPE_ERROR
import GHC.TypeLits (TypeError, ErrorMessage (..))
#endif
class HasServer api context where
type ServerT api (m :: * -> *) :: *
2014-12-10 16:10:57 +01:00
route ::
Proxy api
-> Context context
-> Delayed env (Server api)
-> Router env
2014-12-28 23:07:14 +01:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext
2017-09-08 18:21:16 +02:00
:: Proxy api
-> Proxy context
-> (forall x. m x -> n x)
-> ServerT api m
-> ServerT api n
type Server api = ServerT api Handler
2014-12-10 16:10:57 +01:00
-- * Instances
2014-12-28 23:07:14 +01:00
-- | A server for @a ':<|>' b@ first tries to match the request against the route
2014-12-10 16:10:57 +01:00
-- represented by @a@ and if it fails tries @b@. You must provide a request
-- handler for each route.
--
2015-01-13 20:40:41 +01:00
-- > type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
-- > :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books
2014-12-10 16:10:57 +01:00
-- >
-- > server :: Server MyApi
-- > server = listAllBooks :<|> postBook
-- > where listAllBooks = ...
-- > postBook book = ...
2016-02-28 23:23:32 +01:00
instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) context where
2015-05-03 01:45:17 +02:00
type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
2016-02-28 23:23:32 +01:00
route Proxy context server = choice (route pa context ((\ (a :<|> _) -> a) <$> server))
(route pb context ((\ (_ :<|> b) -> b) <$> server))
2014-12-10 16:10:57 +01:00
where pa = Proxy :: Proxy a
pb = Proxy :: Proxy b
-- | This is better than 'enter', as it's tailor made for 'HasServer'.
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt (a :<|> b) =
hoistServerWithContext (Proxy :: Proxy a) pc nt a :<|>
hoistServerWithContext (Proxy :: Proxy b) pc nt b
2017-09-08 18:21:16 +02:00
2014-12-10 16:10:57 +01:00
-- | If you use 'Capture' in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a function
-- that takes an argument of the type specified by the 'Capture'.
-- This lets servant worry about getting it from the URL and turning
-- it into a value of the type you specify.
--
-- You can control how it'll be converted from 'Text' to your type
-- by simply providing an instance of 'FromHttpApiData' for your type.
2014-12-10 16:10:57 +01:00
--
-- Example:
--
2015-01-13 20:40:41 +01:00
-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
2014-12-10 16:10:57 +01:00
-- >
-- > server :: Server MyApi
-- > server = getBook
-- > where getBook :: Text -> Handler Book
2014-12-10 16:10:57 +01:00
-- > getBook isbn = ...
instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
=> HasServer (Capture capture a :> api) context where
2014-12-10 16:10:57 +01:00
type ServerT (Capture capture a :> api) m =
a -> ServerT api m
2014-12-10 16:10:57 +01:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
2016-02-28 23:23:32 +01:00
route Proxy context d =
CaptureRouter $
route (Proxy :: Proxy api)
2016-02-28 23:23:32 +01:00
context
2016-05-26 20:10:15 +02:00
(addCapture d $ \ txt -> case parseUrlPieceMaybe txt of
Nothing -> delayedFail err400
Just v -> return v
)
2015-01-12 15:08:41 +01:00
2016-05-26 20:10:15 +02:00
-- | If you use 'CaptureAll' in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a
-- function that takes an argument of a list of the type specified by
-- the 'CaptureAll'. This lets servant worry about getting values from
-- the URL and turning them into values of the type you specify.
--
-- You can control how they'll be converted from 'Text' to your type
-- by simply providing an instance of 'FromHttpApiData' for your type.
--
-- Example:
--
-- > type MyApi = "src" :> CaptureAll "segments" Text :> Get '[JSON] SourceFile
-- >
-- > server :: Server MyApi
-- > server = getSourceFile
-- > where getSourceFile :: [Text] -> Handler Book
-- > getSourceFile pathSegments = ...
2017-09-08 18:21:16 +02:00
instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
=> HasServer (CaptureAll capture a :> api) context where
2016-05-26 20:10:15 +02:00
2017-09-08 18:21:16 +02:00
type ServerT (CaptureAll capture a :> api) m =
[a] -> ServerT api m
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2016-05-26 20:10:15 +02:00
route Proxy context d =
CaptureAllRouter $
2017-09-08 18:21:16 +02:00
route (Proxy :: Proxy api)
2016-05-26 20:10:15 +02:00
context
(addCapture d $ \ txts -> case parseUrlPieces txts of
Left _ -> delayedFail err400
Right v -> return v
)
allowedMethodHead :: Method -> Request -> Bool
allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
allowedMethod :: Method -> Request -> Bool
allowedMethod method request = allowedMethodHead method request || requestMethod request == method
methodCheck :: Method -> Request -> DelayedIO ()
methodCheck method request
| allowedMethod method request = return ()
| otherwise = delayedFail err405
-- This has switched between using 'Fail' and 'FailFatal' a number of
-- times. If the 'acceptCheck' is run after the body check (which would
-- be morally right), then we have to set this to 'FailFatal', because
-- the body check is not reversible, and therefore backtracking after the
-- body check is no longer an option. However, we now run the accept
-- check before the body check and can therefore afford to make it
-- recoverable.
acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
acceptCheck proxy accH
| canHandleAcceptH proxy (AcceptHeader accH) = return ()
| otherwise = delayedFail err406
methodRouter :: (AllCTRender ctypes a)
=> (b -> ([(HeaderName, B.ByteString)], a))
-> Method -> Proxy ctypes -> Status
-> Delayed env (Handler b)
-> Router env
methodRouter splitHeaders method proxy status action = leafRouter route'
where
route' env request respond =
let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
in runAction (action `addMethodCheck` methodCheck method request
`addAcceptCheck` acceptCheck proxy accH
) env request respond $ \ output -> do
let (headers, b) = splitHeaders output
case handleAcceptH proxy (AcceptHeader accH) b of
Nothing -> FailFatal err406 -- this should not happen (checked before), so we make it fatal if it does
Just (contentT, body) ->
let bdy = if allowedMethodHead method request then "" else body
in Route $ responseLBS status ((hContentType, cs contentT) : headers) bdy
2015-12-27 17:54:29 +01:00
instance OVERLAPPABLE_
( AllCTRender ctypes a, ReflectMethod method, KnownNat status
2016-02-28 23:23:32 +01:00
) => HasServer (Verb method status ctypes a) context where
type ServerT (Verb method status ctypes a) m = m a
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ _ nt s = nt s
2014-12-10 16:10:57 +01:00
route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
where method = reflectMethod (Proxy :: Proxy method)
status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
2015-12-27 17:54:29 +01:00
instance OVERLAPPING_
( AllCTRender ctypes a, ReflectMethod method, KnownNat status
, GetHeaders (Headers h a)
2016-02-28 23:23:32 +01:00
) => HasServer (Verb method status ctypes (Headers h a)) context where
2014-12-10 16:10:57 +01:00
type ServerT (Verb method status ctypes (Headers h a)) m = m (Headers h a)
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ _ nt s = nt s
route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
where method = reflectMethod (Proxy :: Proxy method)
status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
2017-10-19 00:43:43 +02:00
2017-10-19 23:41:49 +02:00
instance OVERLAPPABLE_
( MimeRender ctype a, ReflectMethod method,
FramingRender framing ctype, ToStreamGenerator f a
) => HasServer (Stream method framing ctype (f a)) context where
type ServerT (Stream method framing ctype (f a)) m = m (f a)
hoistServerWithContext _ _ nt s = nt s
route Proxy _ = streamRouter ([],) method (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
where method = reflectMethod (Proxy :: Proxy method)
instance OVERLAPPING_
( MimeRender ctype a, ReflectMethod method,
FramingRender framing ctype, ToStreamGenerator f a,
GetHeaders (Headers h (f a))
) => HasServer (Stream method framing ctype (Headers h (f a))) context where
2017-10-19 00:43:43 +02:00
2017-10-19 23:41:49 +02:00
type ServerT (Stream method framing ctype (Headers h (f a))) m = m (Headers h (f a))
2017-10-19 00:43:43 +02:00
hoistServerWithContext _ _ nt s = nt s
2017-10-19 23:41:49 +02:00
route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
where method = reflectMethod (Proxy :: Proxy method)
streamRouter :: (MimeRender ctype a, FramingRender framing ctype, ToStreamGenerator f a) =>
(b -> ([(HeaderName, B.ByteString)], f a))
-> Method
-> Proxy framing
-> Proxy ctype
-> Delayed env (Handler b)
-> Router env
streamRouter splitHeaders method framingproxy ctypeproxy action = leafRouter $ \env request respond ->
2017-10-19 00:43:43 +02:00
let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
2017-10-19 23:41:49 +02:00
cmediatype = NHM.matchAccept [contentType ctypeproxy] accH
2017-10-19 00:43:43 +02:00
accCheck = when (isNothing cmediatype) $ delayedFail err406
contentHeader = (hContentType, NHM.renderHeader . maybeToList $ cmediatype)
in runAction (action `addMethodCheck` methodCheck method request
`addAcceptCheck` accCheck
2017-10-19 23:41:49 +02:00
) env request respond $ \ output ->
let (headers, fa) = splitHeaders output
k = getStreamGenerator . toStreamGenerator $ fa in
Route $ responseStream status200 (contentHeader : headers) $ \write flush -> do
write . BB.lazyByteString $ header framingproxy ctypeproxy
case boundary framingproxy ctypeproxy of
2017-10-19 00:43:43 +02:00
BoundaryStrategyBracket f ->
let go x = let bs = mimeRender ctypeproxy x
2017-10-19 00:43:43 +02:00
(before, after) = f bs
in write ( BB.lazyByteString before
<> BB.lazyByteString bs
2017-10-19 23:41:49 +02:00
<> BB.lazyByteString after) >> flush
2017-10-19 00:43:43 +02:00
in k go go
BoundaryStrategyIntersperse sep -> k
(\x -> do
2017-10-19 23:41:49 +02:00
write . BB.lazyByteString . mimeRender ctypeproxy $ x
2017-10-19 00:43:43 +02:00
flush)
(\x -> do
2017-10-19 23:41:49 +02:00
write . (BB.lazyByteString sep <>) . BB.lazyByteString . mimeRender ctypeproxy $ x
2017-10-19 00:43:43 +02:00
flush)
2017-10-19 23:41:49 +02:00
BoundaryStrategyGeneral f ->
let go = (>> flush) . write . BB.lazyByteString . f . mimeRender ctypeproxy
in k go go
2017-10-20 21:09:11 +02:00
write . BB.lazyByteString $ trailer framingproxy ctypeproxy
2017-10-19 00:43:43 +02:00
2014-12-10 16:10:57 +01:00
-- | If you use 'Header' in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a function
-- that takes an argument of the type specified by 'Header'.
-- This lets servant worry about extracting it from the request and turning
-- it into a value of the type you specify.
--
-- All it asks is for a 'FromHttpApiData' instance.
2014-12-10 16:10:57 +01:00
--
-- Example:
--
-- > newtype Referer = Referer Text
2016-04-02 09:03:00 +02:00
-- > deriving (Eq, Show, FromHttpApiData)
2014-12-10 16:10:57 +01:00
-- >
-- > -- GET /view-my-referer
2015-01-13 20:40:41 +01:00
-- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get '[JSON] Referer
2014-12-10 16:10:57 +01:00
-- >
-- > server :: Server MyApi
-- > server = viewReferer
-- > where viewReferer :: Referer -> Handler referer
2014-12-10 16:10:57 +01:00
-- > viewReferer referer = return referer
instance
(KnownSymbol sym, FromHttpApiData a, HasServer api context
, SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
)
=> HasServer (Header' mods sym a :> api) context where
------
type ServerT (Header' mods sym a :> api) m =
RequestArgument mods a -> ServerT api m
2014-12-10 16:10:57 +01:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
route Proxy context subserver = route (Proxy :: Proxy api) context $
subserver `addHeaderCheck` withRequest headerCheck
where
headerName :: IsString n => n
headerName = fromString $ symbolVal (Proxy :: Proxy sym)
headerCheck :: Request -> DelayedIO (RequestArgument mods a)
headerCheck req =
unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
where
mev :: Maybe (Either T.Text a)
mev = fmap parseHeader $ lookup headerName (requestHeaders req)
errReq = delayedFailFatal err400
{ errBody = "Header " <> headerName <> " is required"
}
errSt e = delayedFailFatal err400
{ errBody = cs $ "Error parsing header "
<> headerName
<> " failed: " <> e
}
2014-12-10 16:10:57 +01:00
-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a function
-- that takes an argument of type @'Maybe' 'Text'@.
--
-- This lets servant worry about looking it up in the query string
-- and turning it into a value of the type you specify, enclosed
-- in 'Maybe', because it may not be there and servant would then
-- hand you 'Nothing'.
--
-- You can control how it'll be converted from 'Text' to your type
-- by simply providing an instance of 'FromHttpApiData' for your type.
2014-12-10 16:10:57 +01:00
--
-- Example:
--
2015-01-13 20:40:41 +01:00
-- > type MyApi = "books" :> QueryParam "author" Text :> Get '[JSON] [Book]
2014-12-10 16:10:57 +01:00
-- >
-- > server :: Server MyApi
-- > server = getBooksBy
-- > where getBooksBy :: Maybe Text -> Handler [Book]
2014-12-10 16:10:57 +01:00
-- > getBooksBy Nothing = ...return all books...
-- > getBooksBy (Just author) = ...return books by the given author...
instance
( KnownSymbol sym, FromHttpApiData a, HasServer api context
, SBoolI (FoldRequired mods), SBoolI (FoldLenient mods)
)
=> HasServer (QueryParam' mods sym a :> api) context where
------
type ServerT (QueryParam' mods sym a :> api) m =
RequestArgument mods a -> ServerT api m
2014-12-10 16:10:57 +01:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
route Proxy context subserver =
let querytext req = parseQueryText $ rawQueryString req
paramname = cs $ symbolVal (Proxy :: Proxy sym)
parseParam :: Request -> DelayedIO (RequestArgument mods a)
parseParam req =
unfoldRequestArgument (Proxy :: Proxy mods) errReq errSt mev
where
mev :: Maybe (Either T.Text a)
mev = fmap parseQueryParam $ join $ lookup paramname $ querytext req
errReq = delayedFailFatal err400
{ errBody = cs $ "Query parameter " <> paramname <> " is required"
}
errSt e = delayedFailFatal err400
{ errBody = cs $ "Error parsing query parameter "
<> paramname <> " failed: " <> e
}
delayed = addParameterCheck subserver . withRequest $ \req ->
parseParam req
in route (Proxy :: Proxy api) context delayed
2014-12-10 16:10:57 +01:00
-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a function
-- that takes an argument of type @['Text']@.
--
-- This lets servant worry about looking up 0 or more values in the query string
-- associated to @authors@ and turning each of them into a value of
-- the type you specify.
--
-- You can control how the individual values are converted from 'Text' to your type
-- by simply providing an instance of 'FromHttpApiData' for your type.
2014-12-10 16:10:57 +01:00
--
-- Example:
--
2015-01-13 20:40:41 +01:00
-- > type MyApi = "books" :> QueryParams "authors" Text :> Get '[JSON] [Book]
2014-12-10 16:10:57 +01:00
-- >
-- > server :: Server MyApi
-- > server = getBooksBy
-- > where getBooksBy :: [Text] -> Handler [Book]
2014-12-10 16:10:57 +01:00
-- > getBooksBy authors = ...return all books by these authors...
instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
=> HasServer (QueryParams sym a :> api) context where
2014-12-10 16:10:57 +01:00
type ServerT (QueryParams sym a :> api) m =
[a] -> ServerT api m
2014-12-10 16:10:57 +01:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
2017-01-16 13:17:20 +01:00
route Proxy context subserver = route (Proxy :: Proxy api) context $
subserver `addParameterCheck` withRequest paramsCheck
where
paramname = cs $ symbolVal (Proxy :: Proxy sym)
paramsCheck req =
case partitionEithers $ fmap parseQueryParam params of
([], parsed) -> return parsed
(errs, _) -> delayedFailFatal err400
{ errBody = cs $ "Error parsing query parameter(s) "
<> paramname <> " failed: "
<> T.intercalate ", " errs
2017-01-16 13:17:20 +01:00
}
where
params :: [T.Text]
params = mapMaybe snd
. filter (looksLikeParam . fst)
. parseQueryText
. rawQueryString
$ req
looksLikeParam name = name == paramname || name == (paramname <> "[]")
2014-12-10 16:10:57 +01:00
-- | If you use @'QueryFlag' "published"@ in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a function
-- that takes an argument of type 'Bool'.
--
-- Example:
--
2015-01-13 20:40:41 +01:00
-- > type MyApi = "books" :> QueryFlag "published" :> Get '[JSON] [Book]
2014-12-10 16:10:57 +01:00
-- >
-- > server :: Server MyApi
-- > server = getBooks
-- > where getBooks :: Bool -> Handler [Book]
2014-12-10 16:10:57 +01:00
-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
instance (KnownSymbol sym, HasServer api context)
=> HasServer (QueryFlag sym :> api) context where
2014-12-10 16:10:57 +01:00
type ServerT (QueryFlag sym :> api) m =
Bool -> ServerT api m
2014-12-10 16:10:57 +01:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
route Proxy context subserver =
let querytext r = parseQueryText $ rawQueryString r
param r = case lookup paramname (querytext r) of
2014-12-10 16:10:57 +01:00
Just Nothing -> True -- param is there, with no value
Just (Just v) -> examine v -- param with a value
Nothing -> False -- param not in the query string
in route (Proxy :: Proxy api) context (passToServer subserver param)
2014-12-10 16:10:57 +01:00
where paramname = cs $ symbolVal (Proxy :: Proxy sym)
examine v | v == "true" || v == "1" || v == "" = True
| otherwise = False
-- | Just pass the request to the underlying application and serve its response.
--
-- Example:
--
-- > type MyApi = "images" :> Raw
-- >
-- > server :: Server MyApi
-- > server = serveDirectory "/var/www/images"
2016-02-28 23:23:32 +01:00
instance HasServer Raw context where
type ServerT Raw m = Tagged m Application
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ _ _ = retag
2017-09-08 18:21:16 +02:00
2017-01-18 11:25:18 +01:00
route Proxy _ rawApplication = RawRouter $ \ env request respond -> runResourceT $ do
-- note: a Raw application doesn't register any cleanup
-- but for the sake of consistency, we nonetheless run
-- the cleanup once its done
2017-01-18 10:37:18 +01:00
r <- runDelayed rawApplication env request
2017-01-18 11:25:18 +01:00
liftIO $ go r request respond
where go r request respond = case r of
Route app -> untag app request (respond . Route)
Fail a -> respond $ Fail a
FailFatal e -> respond $ FailFatal e
2014-12-10 16:10:57 +01:00
-- | If you use 'ReqBody' in one of the endpoints for your API,
-- this automatically requires your server-side handler to be a function
-- that takes an argument of the type specified by 'ReqBody'.
2015-01-13 20:40:41 +01:00
-- The @Content-Type@ header is inspected, and the list provided is used to
-- attempt deserialization. If the request does not have a @Content-Type@
2015-09-15 17:21:41 +02:00
-- header, it is treated as @application/octet-stream@ (as specified in
-- <http://tools.ietf.org/html/rfc7231#section-3.1.1.5 RFC7231>.
2014-12-10 16:10:57 +01:00
-- This lets servant worry about extracting it from the request and turning
-- it into a value of the type you specify.
--
2015-01-13 20:40:41 +01:00
--
2014-12-10 16:10:57 +01:00
-- All it asks is for a 'FromJSON' instance.
--
-- Example:
--
2015-01-13 20:40:41 +01:00
-- > type MyApi = "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book
2014-12-10 16:10:57 +01:00
-- >
-- > server :: Server MyApi
-- > server = postBook
-- > where postBook :: Book -> Handler Book
2014-12-10 16:10:57 +01:00
-- > postBook book = ...insert into your db...
instance ( AllCTUnrender list a, HasServer api context, SBoolI (FoldLenient mods)
) => HasServer (ReqBody' mods list a :> api) context where
2014-12-10 16:10:57 +01:00
type ServerT (ReqBody' mods list a :> api) m =
If (FoldLenient mods) (Either String a) a -> ServerT api m
2014-12-10 16:10:57 +01:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
2017-01-16 13:17:20 +01:00
route Proxy context subserver
= route (Proxy :: Proxy api) context $
addBodyCheck subserver ctCheck bodyCheck
where
2017-01-16 13:17:20 +01:00
-- Content-Type check, we only lookup we can try to parse the request body
ctCheck = withRequest $ \ request -> do
-- See HTTP RFC 2616, section 7.2.1
-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
-- See also "W3C Internet Media Type registration, consistency of use"
-- http://www.w3.org/2001/tag/2002/0129-mime
let contentTypeH = fromMaybe "application/octet-stream"
$ lookup hContentType $ requestHeaders request
2017-01-16 13:17:20 +01:00
case canHandleCTypeH (Proxy :: Proxy list) (cs contentTypeH) :: Maybe (BL.ByteString -> Either String a) of
Nothing -> delayedFailFatal err415
Just f -> return f
-- Body check, we get a body parsing functions as the first argument.
bodyCheck f = withRequest $ \ request -> do
mrqbody <- f <$> liftIO (lazyRequestBody request)
case sbool :: SBool (FoldLenient mods) of
STrue -> return mrqbody
SFalse -> case mrqbody of
Left e -> delayedFailFatal err400 { errBody = cs e }
Right v -> return v
2014-12-10 16:10:57 +01:00
-- | Make sure the incoming request starts with @"/path"@, strip it and
-- pass the rest of the request path to @api@.
instance (KnownSymbol path, HasServer api context) => HasServer (path :> api) context where
type ServerT (path :> api) m = ServerT api m
route Proxy context subserver =
pathRouter
(cs (symbolVal proxyPath))
(route (Proxy :: Proxy api) context subserver)
2014-12-10 16:10:57 +01:00
where proxyPath = Proxy :: Proxy path
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt s
2016-02-28 23:23:32 +01:00
instance HasServer api context => HasServer (RemoteHost :> api) context where
type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
route Proxy context subserver =
route (Proxy :: Proxy api) context (passToServer subserver remoteHost)
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2016-02-28 23:23:32 +01:00
instance HasServer api context => HasServer (IsSecure :> api) context where
type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
route Proxy context subserver =
route (Proxy :: Proxy api) context (passToServer subserver secure)
where secure req = if isSecure req then Secure else NotSecure
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
2016-02-28 23:23:32 +01:00
instance HasServer api context => HasServer (Vault :> api) context where
type ServerT (Vault :> api) m = Vault -> ServerT api m
route Proxy context subserver =
route (Proxy :: Proxy api) context (passToServer subserver vault)
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2016-02-28 23:23:32 +01:00
instance HasServer api context => HasServer (HttpVersion :> api) context where
type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
route Proxy context subserver =
route (Proxy :: Proxy api) context (passToServer subserver httpVersion)
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-06-08 17:27:36 +02:00
-- | Ignore @'Summary'@ in server handlers.
instance HasServer api ctx => HasServer (Summary desc :> api) ctx where
type ServerT (Summary desc :> api) m = ServerT api m
route _ = route (Proxy :: Proxy api)
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt s
2017-06-08 17:27:36 +02:00
-- | Ignore @'Description'@ in server handlers.
instance HasServer api ctx => HasServer (Description desc :> api) ctx where
type ServerT (Description desc :> api) m = ServerT api m
route _ = route (Proxy :: Proxy api)
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt s
2017-06-08 17:27:36 +02:00
-- | Singleton type representing a server that serves an empty API.
data EmptyServer = EmptyServer deriving (Typeable, Eq, Show, Bounded, Enum)
2017-05-16 11:34:07 +02:00
2017-05-16 13:09:54 +02:00
-- | Server for `EmptyAPI`
emptyServer :: ServerT EmptyAPI m
2017-05-16 17:59:41 +02:00
emptyServer = Tagged EmptyServer
2017-05-16 11:34:07 +02:00
2017-05-16 13:09:54 +02:00
-- | The server for an `EmptyAPI` is `emptyAPIServer`.
--
-- > type MyApi = "nothing" :> EmptyApi
-- >
-- > server :: Server MyApi
-- > server = emptyAPIServer
2017-05-16 11:34:07 +02:00
instance HasServer EmptyAPI context where
type ServerT EmptyAPI m = Tagged m EmptyServer
2017-05-16 11:34:07 +02:00
route Proxy _ _ = StaticRouter mempty mempty
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ _ _ = retag
2017-09-08 18:21:16 +02:00
-- | Basic Authentication
instance ( KnownSymbol realm
2016-03-08 23:28:27 +01:00
, HasServer api context
, HasContextEntry context (BasicAuthCheck usr)
)
2016-03-08 23:28:27 +01:00
=> HasServer (BasicAuth realm usr :> api) context where
type ServerT (BasicAuth realm usr :> api) m = usr -> ServerT api m
route Proxy context subserver =
route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck)
where
realm = BC8.pack $ symbolVal (Proxy :: Proxy realm)
2016-03-08 23:28:27 +01:00
basicAuthContext = getContextEntry context
authCheck = withRequest $ \ req -> runBasicAuth req realm basicAuthContext
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
2017-09-08 18:21:16 +02:00
-- * helpers
ct_wildcard :: B.ByteString
ct_wildcard = "*" <> "/" <> "*" -- Because CPP
2016-01-14 23:43:48 +01:00
-- * General Authentication
2016-02-28 23:23:32 +01:00
-- * contexts
2016-01-14 23:43:48 +01:00
2016-02-28 23:23:32 +01:00
instance (HasContextEntry context (NamedContext name subContext), HasServer subApi subContext)
=> HasServer (WithNamedContext name subContext subApi) context where
2016-02-28 23:23:32 +01:00
type ServerT (WithNamedContext name subContext subApi) m =
2016-01-14 23:43:48 +01:00
ServerT subApi m
2016-02-28 23:23:32 +01:00
route Proxy context delayed =
route subProxy subContext delayed
2016-01-14 23:43:48 +01:00
where
subProxy :: Proxy subApi
subProxy = Proxy
2016-02-28 23:23:32 +01:00
subContext :: Context subContext
subContext = descendIntoNamedContext (Proxy :: Proxy name) context
2017-09-08 18:21:16 +02:00
2017-10-01 18:20:09 +02:00
hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
-------------------------------------------------------------------------------
-- TypeError helpers
-------------------------------------------------------------------------------
#ifdef HAS_TYPE_ERROR
-- | This instance catches mistakes when there are non-saturated
-- type applications on LHS of ':>'.
--
-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
-- ...
-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
-- ...Maybe you haven't applied enough arguments to
-- ...Capture "foo"
-- ...
--
-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
-- ...
-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
-- ...Maybe you haven't applied enough arguments to
-- ...Capture "foo"
-- ...
--
instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
where
type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
-- it doens't really matter what sub route we peak
route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
hoistServerWithContext _ _ _ = id
-- Cannot have TypeError here, otherwise use of this symbol will error :)
type HasServerArrowKindError arr =
'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
':$$: 'Text "Maybe you haven't applied enough arguments to"
':$$: 'ShowType arr
-- | This instance prevents from accidentally using '->' instead of ':>'
--
-- >>> serve (Proxy :: Proxy (Capture "foo" Int -> Get '[JSON] Int)) (error "...")
-- ...
-- ...No instance HasServer (a -> b).
-- ...Maybe you have used '->' instead of ':>' between
-- ...Capture "foo" Int
-- ...and
-- ...Verb 'GET 200 '[JSON] Int
-- ...
--
-- >>> undefined :: Server (Capture "foo" Int -> Get '[JSON] Int)
-- ...
-- ...No instance HasServer (a -> b).
-- ...Maybe you have used '->' instead of ':>' between
-- ...Capture "foo" Int
-- ...and
-- ...Verb 'GET 200 '[JSON] Int
-- ...
--
instance TypeError (HasServerArrowTypeError a b) => HasServer (a -> b) context
where
type ServerT (a -> b) m = TypeError (HasServerArrowTypeError a b)
route _ _ _ = error "servant-server panic: impossible happened in HasServer (a -> b)"
hoistServerWithContext _ _ _ = id
type HasServerArrowTypeError a b =
'Text "No instance HasServer (a -> b)."
':$$: 'Text "Maybe you have used '->' instead of ':>' between "
':$$: 'ShowType a
':$$: 'Text "and"
':$$: 'ShowType b
#endif
-- $setup
-- >>> import Servant