module Control.Monad.Apiary.Filter (
Filter, Filter'
, method
, http09, http10, http11
, root
, capture
, (??)
, (=:), (=!:), (=?:), (=?!:), (=*:), (=+:)
, switchQuery
, eqHeader
, header
, jsonReqBody
, accept
, ssl
, HasDesc(..)
, QueryKey(..)
, query
, Control.Monad.Apiary.Filter.httpVersion
, function, function', function_, focus
, Doc(..)
) where
import qualified Network.Wai as Wai
import Network.Wai.Parse (parseContentType, parseHttpAccept)
import qualified Network.HTTP.Types as HTTP
import Control.Monad(mzero)
import Control.Monad.Trans(MonadIO)
import Control.Monad.Apiary.Action.Internal
( getQueryParams, getReqBodyInternal
, getRequest, getReqBodyJSON, ContentType, contentType
, getConfig, ApiaryConfig(..)
)
import Control.Monad.Apiary.Filter.Internal
( function, function', function_
, Doc(DocMethod, DocPrecondition, DocRoot, DocQuery, DocAccept)
)
import Control.Monad.Apiary.Filter.Internal.Capture.TH(capture)
import Control.Monad.Apiary.Internal(Filter, Filter', focus)
import Text.Blaze.Html(Html, toHtml)
import qualified Data.ByteString.Char8 as SC
import qualified Data.Text as T
import qualified Data.CaseInsensitive as CI
import Data.Monoid((<>))
import Data.Proxy(Proxy(..))
import GHC.TypeLits(KnownSymbol, Symbol, symbolVal)
import Data.Apiary.SProxy(SProxy(..))
import Data.Apiary.Routing.Dict(type (</), KV((:=)))
import qualified Data.Apiary.Routing.Dict as Dict
import qualified Data.Apiary.Routing as R
import Data.Apiary.Param
( ReqParam, StrategyRep(..), QueryRep(NoValue)
, Strategy(..), reqParamRep, reqParams
, pFirst, pOne, pOption, pOptional, pMany, pSome
)
import Data.Apiary.Method(Method)
import Data.Aeson (FromJSON)
method :: Monad actM => Method -> Filter' exts actM m
method m = focus (DocMethod m) (Just m) id
ssl :: Monad actM => Filter' exts actM m
ssl = function_ (DocPrecondition "SSL required") Wai.isSecure
httpVersion :: Monad actM => HTTP.HttpVersion -> Html -> Filter' exts actM m
httpVersion v h = function_ (DocPrecondition h) $ (v ==) . Wai.httpVersion
http09 :: Monad actM => Filter' exts actM m
http09 = Control.Monad.Apiary.Filter.httpVersion HTTP.http09 "HTTP/0.9 only"
http10 :: Monad actM => Filter' exts actM m
http10 = Control.Monad.Apiary.Filter.httpVersion HTTP.http10 "HTTP/1.0 only"
http11 :: Monad actM => Filter' exts actM m
http11 = Control.Monad.Apiary.Filter.httpVersion HTTP.http11 "HTTP/1.1 only"
root :: (Monad m, Monad actM) => Filter' exts actM m
root = focus DocRoot Nothing $ R.raw "ROOT" $ \d r -> do
roots <- rootPattern `fmap` getConfig
case r of
[] -> return (d, [])
[p] | p `elem` roots -> return (d, [])
_ -> mzero
newtype QueryKey (key :: Symbol) = QueryKey { queryKeyDesc :: Maybe Html }
(??) :: proxy key -> Html -> QueryKey key
_ ?? d = QueryKey (Just d)
class HasDesc (a :: Symbol -> *) where
queryDesc :: a key -> Maybe Html
instance HasDesc QueryKey where
queryDesc = queryKeyDesc
instance HasDesc Proxy where
queryDesc = const Nothing
instance HasDesc SProxy where
queryDesc = const Nothing
query :: forall query strategy k v exts prms actM m. (k </ prms, MonadIO actM, KnownSymbol k, ReqParam v, HasDesc query, Strategy strategy)
=> query k -> strategy v -> Filter exts actM m prms (SNext strategy k v prms)
query k w = focus doc Nothing $ R.raw "query" $ \d t -> do
qs <- getQueryParams
(ps,fs) <- getReqBodyInternal
let as = map snd . filter ((SC.pack (symbolVal k) ==) . fst) $ reqParams (Proxy :: Proxy v) qs ps fs
case strategy w k as d of
Nothing -> mzero
Just d' -> return (d', t)
where
doc = DocQuery (T.pack $ symbolVal k) (strategyRep w) (reqParamRep (Proxy :: Proxy v)) (queryDesc k)
(=:) :: (HasDesc query, MonadIO actM, ReqParam v, KnownSymbol k, k </ prms)
=> query k -> proxy v -> Filter exts actM m prms (k ':= v ': prms)
k =: v = query k (pFirst v)
(=!:) :: (HasDesc query, MonadIO actM, ReqParam v, KnownSymbol k, k </ prms)
=> query k -> proxy v -> Filter exts actM m prms (k ':= v ': prms)
k =!: t = query k (pOne t)
(=?:) :: (HasDesc query, MonadIO actM, ReqParam v, KnownSymbol k, k </ prms)
=> query k -> proxy v -> Filter exts actM m prms (k ':= Maybe v ': prms)
k =?: t = query k (pOption t)
(=?!:) :: forall query k v exts prms actM m. (HasDesc query, MonadIO actM, Show v, ReqParam v, KnownSymbol k, k </ prms)
=> query k -> v -> Filter exts actM m prms (k ':= v ': prms)
k =?!: v = query k (pOptional v)
(=*:) :: (HasDesc query, MonadIO actM, ReqParam v, KnownSymbol k, k </ prms)
=> query k -> proxy v -> Filter exts actM m prms (k ':= [v] ': prms)
k =*: t = query k (pMany t)
(=+:) :: (HasDesc query, MonadIO actM, ReqParam v, KnownSymbol k, k </ prms)
=> query k -> proxy v -> Filter exts actM m prms (k ':= [v] ': prms)
k =+: t = query k (pSome t)
switchQuery :: (HasDesc proxy, MonadIO actM, KnownSymbol k, k </ prms)
=> proxy k -> Filter exts actM m prms (k ':= Bool ': prms)
switchQuery k = focus doc Nothing $ R.raw "switch" $ \d t -> do
qs <- getQueryParams
(ps,fs) <- getReqBodyInternal
let n = maybe False id . fmap (maybe True id) . lookup (SC.pack $ symbolVal k) $ reqParams (Proxy :: Proxy Bool) qs ps fs
return (Dict.add k n d, t)
where
doc = (DocQuery (T.pack $ symbolVal k) (StrategyRep "switch") NoValue (queryDesc k))
header :: (KnownSymbol k, Monad actM, k </ prms)
=> proxy k -> Filter exts actM m prms (k ':= SC.ByteString ': prms)
header k = focus doc Nothing $ R.raw "header" $ \d t -> do
n <- maybe mzero return . lookup (CI.mk . SC.pack $ symbolVal k) . Wai.requestHeaders =<< getRequest
return (Dict.add k n d, t)
where
doc = DocPrecondition $ "has header: " <> toHtml (symbolVal k)
eqHeader :: (KnownSymbol k, Monad actM)
=> proxy k -> SC.ByteString -> Filter' exts actM m
eqHeader k v = focus doc Nothing $ R.raw "=header" $ \d t -> do
v' <- maybe mzero return . lookup (CI.mk . SC.pack $ symbolVal k) . Wai.requestHeaders =<< getRequest
if v == v' then return (d,t) else mzero
where
doc = DocPrecondition $ "header: " <> toHtml (symbolVal k) <> " = " <> toHtml (show v)
jsonReqBody :: (KnownSymbol k, MonadIO actM, k </ prms, FromJSON a)
=> proxy k -> Filter exts actM m prms (k ':= a ': prms)
jsonReqBody k = focus doc Nothing $ R.raw "json body" $ \d t -> do
n <- maybe mzero return =<< getReqBodyJSON
return (Dict.add k n d, t)
where
doc = DocPrecondition $ "json body: " <> toHtml (symbolVal k)
accept :: Monad actM => ContentType -> Filter' exts actM m
accept ect = focus (DocAccept ect) Nothing $ R.raw "accept" $ \d t ->
fmap (lookup "Accept" . Wai.requestHeaders) getRequest >>= \case
Nothing -> mzero
Just ac ->
let ex@(et, _) = parseContentType ect
accepts = map parseContentType (parseHttpAccept ac)
in case filter (matchContentType ex) accepts of
[] -> mzero
(_,p):_ -> contentType (prettyContentType et p) >> return (d, t)
matchContentType :: (SC.ByteString, [(SC.ByteString, SC.ByteString)])
-> (SC.ByteString, [(SC.ByteString, SC.ByteString)])
-> Bool
matchContentType (ct, ep) (acc, ip) = case SC.break (== '/') acc of
("*", "/*") -> prmCheck
(a, "/*") -> a == SC.takeWhile (/= '/') ct && prmCheck
_ -> acc == ct && prmCheck
where
prmCheck = all (\(k,v) -> Just v == lookup k ip) ep
prettyContentType :: SC.ByteString -> [(SC.ByteString, SC.ByteString)] -> SC.ByteString
prettyContentType ct prms =
let pprms = SC.concat $ concatMap (\(k,v) -> [";", k, "=", v]) prms
in ct `SC.append` pprms