{-# LANGUAGE CPP , DataKinds , DeriveDataTypeable , GADTs , KindSignatures , NoImplicitPrelude , TupleSections , TypeFamilies #-} #if MIN_VERSION_base(4,9,0) {-# OPTIONS_GHC -Wno-redundant-constraints #-} #endif -- | Handlers for endpoints in a 'Resource'. module Rest.Handler ( -- * Single handlers. mkHandler , mkInputHandler , mkConstHandler , mkIdHandler -- * Listings. , mkListing , mkOrderedListing -- ** Parameter parsers for listings. , Range (..) , range , orderedRange -- * Generic handlers and core data types. , Env (..) , GenHandler (..) , mkGenHandler , Apply , Handler , ListHandler -- * Convenience functions. , secureHandler ) where import Prelude.Compat import Control.Arrow import Control.Monad.Except () import Control.Monad.Identity import Control.Monad.Reader import Control.Monad.Trans.Except import Rest.Types.Range import Safe import Rest.Dictionary import Rest.Error import Rest.Types.Void ------------------------------------------------------------------------------- -- | An environment of inputs passed to a handler. Contains -- information from the 'header's, the 'param'eters and the body -- 'input'. data Env h p i = Env { header :: h , param :: p , input :: i } -- | A handler for some endpoint. The input and output types are -- specified by the 'dictionary', which can be created using the -- combinators from "Rest.Dictionary.Combinators". The inputs -- (headers, parameters and body) are passed as an 'Env' to the -- 'handler'. This handler runs in monad @m@, combined with the -- ability to throw errors. The result is either the output value, or -- a list of them for list handlers. -- If the 'secure' flag is set, this suggests to clients that the -- resource should only be served over https. It has no effect when -- running the API. data GenHandler m f where GenHandler :: (i ~ FromMaybe () i', o ~ FromMaybe () o', e ~ FromMaybe Void e') => { dictionary :: Dict h p i' o' e' , handler :: Env h p i -> ExceptT (Reason e) m (Apply f o) , secure :: Bool } -> GenHandler m f -- | Construct a 'GenHandler' using a 'Modifier' instead of a 'Dict'. -- The 'secure' flag will be 'False'. mkGenHandler :: (Monad m, i ~ FromMaybe () i', o ~ FromMaybe () o', e ~ FromMaybe Void e') => Modifier h p i' o' e' -> (Env h p i -> ExceptT (Reason e) m (Apply f o)) -> GenHandler m f mkGenHandler d a = GenHandler (d empty) a False -- | Apply a Functor @f@ to a type @a@. In general will result in @f -- a@, except if @f@ is 'Identity', in which case it will result in -- @a@. This prevents a lot of 'Identity' wrapping/unwrapping. type family Apply (f :: * -> *) a :: * type instance Apply Identity a = a type instance Apply [] a = [a] -- | A 'Handler' returning a single item. type Handler m = GenHandler m Identity -- | A 'Handler' returning a list of items. type ListHandler m = GenHandler m [] -- | Set 'secure' to 'True'. secureHandler :: Handler m -> Handler m secureHandler h = h { secure = True } -- | Smart constructor for creating a list handler. -- Restricts the type of the 'Input' dictionary to 'None' mkListing :: (Monad m, o ~ FromMaybe () o', e ~ FromMaybe Void e') => Modifier h p 'Nothing o' e' -> (Range -> ExceptT (Reason e) m [o]) -> ListHandler m mkListing d a = mkGenHandler (mkPar range . d) (a . param) -- | Dictionary for taking 'Range' parameters. Allows two query -- parameters, @offset@ and @count@. If not passed, the defaults are 0 -- and 100. The maximum range that can be passed is 1000. range :: Param Range range = Param ["offset", "count"] $ \xs -> maybe (Left (ParseError "range")) (Right . normalize) $ case xs of [Just o, Just c] -> Range <$> readMay o <*> readMay c [_ , Just c] -> Range 0 <$> readMay c [Just o, _ ] -> (`Range` 100) <$> readMay o _ -> Just $ Range 0 100 where normalize r = Range { offset = max 0 . offset $ r , count = min 1000 . max 0 . count $ r } -- | Create a list handler that accepts ordering information. -- Restricts the type of the 'Input' dictionary to 'None' mkOrderedListing :: (Monad m, o ~ FromMaybe () o', e ~ FromMaybe Void e') => Modifier h p 'Nothing o' e' -> ((Range, Maybe String, Maybe String) -> ExceptT (Reason e) m [o]) -> ListHandler m mkOrderedListing d a = mkGenHandler (mkPar orderedRange . d) (a . param) -- | Dictionary for taking ordering information. In addition to the -- parameters accepted by 'range', this accepts @order@ and -- @direction@. orderedRange :: Param (Range, Maybe String, Maybe String) orderedRange = Param ["offset", "count", "order", "direction"] $ \xs -> case xs of [mo, mc, mor, md] -> maybe (Left (ParseError "range")) (Right . (\(o, c) -> (Range o c, mor, md)) . normalize) $ case (mo, mc) of (Just o, Just c) -> (,) <$> readMay o <*> readMay c (_ , Just c) -> (0,) <$> readMay c (Just o, _ ) -> (,100) <$> readMay o _ -> Just (0, 100) _ -> error "Internal error in orderedRange rest parameters" where normalize = (max 0 *** (min 1000 . max 0)) -- | Create a handler for a single resource. Takes the entire -- environmend as input. mkHandler :: (Monad m, i ~ FromMaybe () i', o ~ FromMaybe () o', e ~ FromMaybe Void e') => Modifier h p i' o' e' -> (Env h p i -> ExceptT (Reason e) m o) -> Handler m mkHandler = mkGenHandler -- | Create a handler for a single resource. Takes only the body -- information as input. mkInputHandler :: (Monad m, i ~ FromMaybe () i', o ~ FromMaybe () o', e ~ FromMaybe Void e') => Modifier () () i' o' e' -> (i -> ExceptT (Reason e) m o) -> Handler m mkInputHandler d a = mkHandler d (a . input) -- | Create a handler for a single resource. Doesn't take any input. mkConstHandler :: (Monad m, o ~ FromMaybe () o', e ~ FromMaybe Void e') => Modifier () () 'Nothing o' e' -> ExceptT (Reason e) m o -> Handler m mkConstHandler d a = mkHandler d (const a) -- | Create a handler for a single resource. Take body information and -- the resource identifier as input. The monad @m@ must be a -- 'Reader'-like type containing the idenfier. mkIdHandler :: (MonadReader id m, i ~ FromMaybe () i', o ~ FromMaybe () o', e ~ FromMaybe Void e') => Modifier h p i' o' e' -> (i -> id -> ExceptT (Reason e) m o) -> Handler m mkIdHandler d a = mkHandler d (\env -> ask >>= a (input env))