eved-0.0.1.0: src/Web/Eved.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Web.Eved
( I.Eved
, (I.:<|>)(..)
, (.</>)
, (.<|>)
, lit
, capture
, reqBody
, queryParam
, verb
, get
, post
, put
, patch
, delete
, runClientIO
, runClient
, noContext
, withContext
, ClientM
, EvedServerT
, server
)
where
import Control.Applicative (liftA2)
import Control.Monad.Reader (Reader, runReader)
import Data.Function ((&))
import Data.Functor.Identity (Identity (..))
import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text)
import Network.HTTP.Types (Status, StdMethod (..), status200)
import Web.Eved.Client
import qualified Web.Eved.ContentType as CT
import qualified Web.Eved.Internal as I
import qualified Web.Eved.QueryParam as QP
import Web.Eved.Server
import qualified Web.Eved.UrlElement as UE
-- |Unwrap an api that requires no context.
-- If none of the combinators that were used required any context use this function
-- to unwrap the api
noContext :: I.Eved api m => Identity (api a) -> api a
noContext = runIdentity
withContext :: I.Eved api m => ctx -> (ctx -> api a) -> api a
withContext = (&)
-- |Combine two sub-api's by trying the left api first and then the right api second.
(.<|>) :: (I.Eved api m, Applicative f) => f (api a) -> f (api b) -> f (api (a I.:<|> b))
(.<|>) =
liftA2 (I..<|>)
-- |Add a Literal string to the path of the api
lit :: (I.Eved api m, Applicative f) => Text -> f (api a) -> f (api a)
lit l = fmap (I.lit l)
-- |Add a url capture with a given name and UrlElement decoder/encoder
capture :: (I.Eved api m, Applicative f) => Text -> f (UE.UrlElement a) -> f (api b) -> f (api (a -> b))
capture t u next = I.capture t <$> u <*> next
-- |Add a request body parser for the given content types
-- The Content-Type header will be examined to assist in content negotiation.
reqBody :: (I.Eved api m, Applicative f) => NonEmpty (f (CT.ContentType a)) -> f (api b) -> f (api (a -> b))
reqBody ctyps next = I.reqBody <$> sequenceA ctyps <*> next
-- |A single query param that is required to exist. If the argument is not required use QP.maybe
queryParam :: (I.Eved api m, Applicative f) => Text -> f (QP.QueryParam a) -> f (api b) -> f (api (a -> b))
queryParam t q next = I.queryParam t <$> q <*> next
-- |A list of query params with the same name (may return an empty list if the param is not specified ever)
queryParams :: (I.Eved api m, Applicative f) => Text -> f (QP.QueryParam a) -> f (api b) -> f (api ([a] -> b))
queryParams t q next = I.queryParam t <$> QP.list q <*> next
-- |The leaf node of most routes, this will specify the HTTP Verb and Status along with a list of ContentType encoder/decoders.
-- The Allow header in the request will be examined to determine a suitable response Content-Type
verb :: (I.Eved api m, Applicative f) => StdMethod -> Status -> NonEmpty (f (CT.ContentType a)) -> f (api (m a))
verb m s ctyps = fmap (I.verb m s) (sequenceA ctyps)
get, post, put, patch, delete :: (I.Eved api m, Applicative f) => NonEmpty (f (CT.ContentType a)) -> f (api (m a))
-- | HTTP GET -- see verb for more info
get = verb GET status200
-- | HTTP POST -- see verb for more info
post = verb POST status200
-- | HTTP PUT -- see verb for more info
put = verb PUT status200
-- | HTTP PATCH -- see verb for more info
patch = verb PATCH status200
-- | HTTP DELETE -- see verb for more info
delete = verb DELETE status200
-- |A Segment seperator to be used between path segments akin to / in a url
-- e.g. lit "hello" .</> capture "name" UE.text .</> get [CT.json @Text]
(.</>) :: (Applicative f, I.Eved api m) => (f (api a) -> f (api b)) -> f (api a) -> f (api b)
(.</>) = ($)
infixr 5 .</>