servant-matrix-param-0.3.2: src/Servant/MatrixParam/Server.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | If you use @'MatrixParam' "author" String@ 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' 'String'@.
--
-- 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.
--
-- Example:
--
-- >>> import Network.Wai
-- >>> import Servant
-- >>>
-- >>> type Api = WithMatrixParams "books" '[MatrixParam "author" String] :> Get '[JSON] [String]
-- >>> let api = Proxy :: Proxy Api
-- >>>
-- >>> -- serveBooks has type @Maybe String -> ExceptT ServantErr IO [String]@
-- >>> let serveBooks mAuthor = return $ case mAuthor of {Just "alice" -> ["Alice's Diary"]; _ -> []}
-- >>>
-- >>> let app = serve api serveBooks :: Application
--
-- You can also have multiple matrix parameters per path segment.
module Servant.MatrixParam.Server where
import Prelude hiding (lookup)
import Data.Proxy
import Data.String.Conversions
import Data.Text as T hiding (last)
import GHC.TypeLits
import Servant.API
import Servant.Server
import Servant.Server.Internal
import Servant.MatrixParam
import Servant.MatrixParam.Server.Internal
import Servant.MatrixParam.Server.Internal.ArgList
instance (KnownSymbol path, HasServer api context
, App (Unapped params (ServerT api Handler)) params
, Apped (Unapped params (ServerT api Handler)) params ~ ServerT api Handler
, ParseArgs params
) =>
HasServer (WithMatrixParams path params :> api) context where
type ServerT (WithMatrixParams path params :> api) m =
Unapped params (ServerT api m)
route Proxy context delayed =
CaptureRouter $
route (Proxy :: Proxy api)
context
(addMatrices delayed $ \ txt -> case parsePathSegment txt of
Nothing -> delayedFail err400
Just segment -> if wantedPath == segmentPath segment
then return $ (parseArgs $ getSegmentParams segment :: ArgList params)
else delayedFail err400
)
where
wantedPath :: Text
wantedPath = cs $ symbolVal (Proxy :: Proxy path)
instance (HasServer api context
, App (Unapped params (ServerT api Handler)) params
, FromHttpApiData captureType
, Apped (Unapped params (ServerT api Handler)) params
~ (ServerT api Handler)
, ParseArgs params
) =>
HasServer (CaptureWithMatrixParams path captureType params :> api) context where
type ServerT (CaptureWithMatrixParams path captureType params :> api) m =
captureType -> Unapped params (ServerT api m)
route Proxy context delayed =
CaptureRouter $
route (Proxy :: Proxy api)
context
(addCapturedMatrices delayed $ \ txt -> case parsePathSegment txt of
Nothing -> delayedFail err400
Just segment -> case parseUrlPiece (segmentPath segment) of
Left _ -> delayedFail err400
Right value -> return
$ (value, parseArgs $ getSegmentParams segment :: ArgList params)
)
addMatrices :: App fn argList => Delayed env fn
-> (captured -> DelayedIO (ArgList argList))
-> Delayed (captured, env) (Apped fn argList)
addMatrices Delayed{..} new =
Delayed
{ capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt
, serverD = \ (x, v) a b req -> (`apply` v) <$> serverD x a b req
, ..
}
addCapturedMatrices
:: ( App restOfFn argList
, fn ~ (captureType -> restOfFn)
, Apped restOfFn argList ~ result)
=> Delayed env fn
-> (captured -> DelayedIO (captureType, ArgList argList))
-> Delayed (captured, env) result
addCapturedMatrices Delayed{..} new =
Delayed
{ capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt
, serverD = \ (x, (capture, matrixParams)) a b req ->
let appCapture = ($ capture) <$> serverD x a b req
in (`apply` matrixParams) <$> appCapture
, ..
}