-- |Routing tables. -- Once you have a number of endpoints defined, you can create a routing table of them using 'routeCase' and 'routes', dynamically combining the various Map representations as necessary to create a single, efficient map. {-# LANGUAGE DeriveFunctor, RecordWildCards, GADTs, Rank2Types, TupleSections #-} module Web.Route.Invertible.Map.Route ( RouteCase , RouteMap , routeCase , routeNormCase , routes , fallbackHEADtoGET , lookupRoute ) where import Prelude hiding (lookup) import Control.Applicative (Alternative(..)) import Control.Invertible.Monoidal.Free import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Trans.State (evalState) import Data.Dynamic (Dynamic, toDyn) import qualified Data.HashMap.Strict as HM import qualified Data.Map.Strict as Map import Data.Semigroup (Semigroup((<>))) import Web.Route.Invertible.String import Web.Route.Invertible.Sequence import Web.Route.Invertible.Path import Web.Route.Invertible.Host import Web.Route.Invertible.ContentType import Web.Route.Invertible.Dynamics import Web.Route.Invertible.Route import Web.Route.Invertible.Request import Web.Route.Invertible.Result import Web.Route.Invertible.Monoid.Exactly import Web.Route.Invertible.Monoid.Prioritized import Web.Route.Invertible.Map.Monoid import Web.Route.Invertible.Map.MonoidHash import Web.Route.Invertible.Map.Default import Web.Route.Invertible.Map.Const import Web.Route.Invertible.Map.Bool import Web.Route.Invertible.Map.Sequence import Web.Route.Invertible.Map.Path import Web.Route.Invertible.Map.Host import Web.Route.Invertible.Map.Method import Web.Route.Invertible.Map.Query import Web.Route.Invertible.Map.Custom -- |A routing table mapping 'Request's to values (actions) @a@. data RouteMapT m a -- These constructors are expected to be nested in order for normalized routes = RouteMapHost !(DefaultMap HostMap (RouteMapT m a)) | RouteMapSecure !(BoolMap (RouteMapT m a)) | RouteMapPath !(DefaultMap PathMap (RouteMapT m a)) | RouteMapMethod !(DefaultMap MethodMap (RouteMapT m a)) | RouteMapQuery !(QueryMap (RouteMapT m a)) | RouteMapAccept !(DefaultMap (MonoidHashMap ContentType) (RouteMapT m a)) | RouteMapCustom !(ConstMap (CustomMap Request Dynamic) (RouteMapT m a)) | RouteMapPriority !(Prioritized (RouteMapT m a)) | RouteMapExactly !(Exactly (m a)) deriving (Show) instance Semigroup (RouteMapT m a) where m <> RouteMapExactly Blank = m RouteMapExactly Blank <> m = m RouteMapHost a <> RouteMapHost b = RouteMapHost (a <> b) RouteMapSecure a <> RouteMapSecure b = RouteMapSecure (a <> b) RouteMapPath a <> RouteMapPath b = RouteMapPath (a <> b) RouteMapMethod a <> RouteMapMethod b = RouteMapMethod (a <> b) RouteMapQuery a <> RouteMapQuery b = RouteMapQuery (a <> b) RouteMapAccept a <> RouteMapAccept b = RouteMapAccept (a <> b) RouteMapCustom a <> RouteMapCustom b = RouteMapCustom (a <> b) RouteMapPriority a <> RouteMapPriority b = RouteMapPriority (a <> b) RouteMapExactly a <> RouteMapExactly b = RouteMapExactly (a <> b) a@ (RouteMapHost _) <> b = a <> RouteMapHost (defaultingValue b) a <> b@(RouteMapHost _) = RouteMapHost (defaultingValue a) <> b a@ (RouteMapSecure _) <> b = a <> RouteMapSecure (singletonBool Nothing b) a <> b@(RouteMapSecure _) = RouteMapSecure (singletonBool Nothing a) <> b a@ (RouteMapPath _) <> b = a <> RouteMapPath (defaultingValue b) a <> b@(RouteMapPath _) = RouteMapPath (defaultingValue a) <> b a@ (RouteMapMethod _) <> b = a <> RouteMapMethod (defaultingValue b) a <> b@(RouteMapMethod _) = RouteMapMethod (defaultingValue a) <> b a@ (RouteMapQuery _) <> b = a <> RouteMapQuery (defaultQueryMap b) a <> b@(RouteMapQuery _) = RouteMapQuery (defaultQueryMap a) <> b a@ (RouteMapAccept _) <> b = a <> RouteMapAccept (defaultingValue b) a <> b@(RouteMapAccept _) = RouteMapAccept (defaultingValue a) <> b a@ (RouteMapCustom _) <> b = a <> RouteMapCustom (constantValue b) a <> b@(RouteMapCustom _) = RouteMapCustom (constantValue a) <> b a@ (RouteMapPriority _) <> b = a <> RouteMapPriority (Prioritized 0 b) a <> b@(RouteMapPriority _) = RouteMapPriority (Prioritized 0 a) <> b instance Monoid (RouteMapT m a) where mempty = RouteMapExactly Blank mappend = (<>) exactlyMap :: m a -> RouteMapT m a exactlyMap = RouteMapExactly . Exactly mapRoutes :: (m a -> RouteMapT n b) -> (RouteMapT m a -> RouteMapT n b) -> RouteMapT m a -> RouteMapT n b mapRoutes _ f (RouteMapHost m) = RouteMapHost $ f <$> m mapRoutes _ f (RouteMapSecure m) = RouteMapSecure $ f <$> m mapRoutes _ f (RouteMapPath m) = RouteMapPath $ f <$> m mapRoutes _ f (RouteMapMethod m) = RouteMapMethod $ f <$> m mapRoutes _ f (RouteMapQuery m) = RouteMapQuery $ f <$> m mapRoutes _ f (RouteMapAccept m) = RouteMapAccept $ f <$> m mapRoutes _ f (RouteMapCustom m) = RouteMapCustom $ f <$> m mapRoutes _ f (RouteMapPriority m) = RouteMapPriority $ f <$> m mapRoutes f _ (RouteMapExactly (Exactly a)) = f a mapRoutes _ _ (RouteMapExactly Blank) = RouteMapExactly Blank mapRoutes _ _ (RouteMapExactly Conflict) = RouteMapExactly Conflict mapTails :: (m a -> RouteMapT n b) -> RouteMapT m a -> RouteMapT n b mapTails f = mapRoutes f (mapTails f) mapRoute :: (m a -> n b) -> RouteMapT m a -> RouteMapT n b mapRoute f = mapTails (exactlyMap . f) instance Functor f => Functor (RouteMapT f) where fmap f = mapRoute (fmap f) instance Applicative f => Applicative (RouteMapT f) where pure = exactlyMap . pure f <*> m = mapTails (\f' -> mapRoute (f' <*>) m) f f *> m = mapTails (\f' -> mapRoute (f' *>) m) f instance Applicative f => Alternative (RouteMapT f) where empty = RouteMapExactly $ empty (<|>) = mappend instance MonadTrans RouteMapT where lift = exactlyMap type RouteState = RouteMapT DynamicState -- |The type of a route map element created from a single 'Route'. -- These may be combined into a final 'RouteMap'. -- (Currently these are in fact the same representation, but this may change.) type RouteCase = RouteMapT ((->) Dynamics) -- |A map for efficiently looking up requests based on a set of individual route specifications. type RouteMap = RouteCase sequenceState :: RouteString s => Sequence s a -> DefaultMap (SequenceMap s) (RouteState a) sequenceState s = defaultingMap $ RouteMapExactly . Exactly <$> singletonSequence s predicateState :: RoutePredicate a -> RouteState a predicateState (RouteHost (HostRev s)) = RouteMapHost $ sequenceState s predicateState (RouteSecure s) = RouteMapSecure $ singletonBool (Just s) $ pure () predicateState (RoutePath (Path s)) = RouteMapPath $ sequenceState s predicateState (RouteMethod m) = RouteMapMethod $ defaultingMap $ MonoidMap $ Map.singleton m $ pure () predicateState (RouteQuery n p) = RouteMapQuery $ RouteMapExactly . Exactly <$> singletonQueryState n p predicateState (RouteAccept t) = RouteMapAccept $ defaultingMap $ MonoidHashMap $ HM.singleton t $ pure () predicateState (RouteCustom f _) = RouteMapCustom $ constantMap $ singletonCustom (fmap toDyn . f) $ RouteMapExactly $ Exactly $ getDynamic predicateState (RoutePriority p) = RouteMapPriority $ Prioritized p $ pure () routeState :: Route a -> RouteState a routeState (Route r) = runFree $ mapFree predicateState r -- |Convert a 'Route' and result generator to a single entry in the routing table. routeCase :: RouteAction a b -> RouteCase b routeCase (RouteAction r f) = mapRoute (\s -> f . evalState s) $ routeState r -- |Combine 'routeCase' and 'normRoute'. -- See the description of 'normRoute' for an explaination. routeNormCase :: RouteAction a b -> RouteCase b routeNormCase (RouteAction r f) = mapRoute (\s -> f . evalState s) $ routeState $ normRoute r -- |Combine a list of routes to a single map. routes :: [RouteCase a] -> RouteMap a routes = mconcat -- |Make any handler for a 'GET' method in the map also apply to 'HEAD' requests, provided there is not an existing handler. -- A number of frameworks can automatically convert your @GET@ responses into @HEAD@ responses, so this is useful (if slightly wasteful) in those cases. fallbackHEADtoGET :: RouteMap a -> RouteMap a fallbackHEADtoGET (RouteMapMethod m) = RouteMapMethod $ fallbackDefaultMethodHEADtoGET $ fallbackHEADtoGET <$> m fallbackHEADtoGET m = mapRoutes exactlyMap fallbackHEADtoGET m -- |Lookup a value in a routing table based on a 'Request'. -- This returns the action returned by the 'route' that can handle this request, wrapped in a 'RouteResult' in case of error. lookupRoute :: Request -> RouteMap a -> RouteResult a lookupRoute q@Request{..} = rr id where rr :: (Dynamics -> Dynamics) -> RouteMap a -> RouteResult a rr _ (RouteMapExactly Blank) = RouteNotFound rr p (RouteMapExactly (Exactly a)) = RouteResult $ a $ p [] rr _ (RouteMapExactly Conflict) = MultipleRoutes rr p (RouteMapPriority (Prioritized _ r)) = rr p r rr p (RouteMapCustom (ConstMap m cm)) = foldMap (\(x, rm) -> rr (p . (x:)) rm) (lookupCustom q m) <> rr p cm rr p (RouteMapAccept m) = mayber p $ lookupDefault (HM.lookup requestContentType . monoidHashMap) m rr p (RouteMapQuery m) = dynr p $ lookupQuery requestQuery m rr p (RouteMapMethod m) = either AllowedMethods (rr p) $ lookupDefaultMethod requestMethod m rr p (RouteMapPath m) = seqr p requestPath m rr p (RouteMapSecure m) = mayber p $ lookupBool requestSecure m rr p (RouteMapHost m) = seqr p requestHost m mayber :: (Dynamics -> Dynamics) -> Maybe (RouteMap a) -> RouteResult a mayber p = maybe RouteNotFound (rr p) dynr :: (Dynamics -> Dynamics) -> [DynamicResult (RouteMap a)] -> RouteResult a dynr p = foldMap (\(x, rm) -> rr (p . (x ++)) rm) seqr :: RouteString s => (Dynamics -> Dynamics) -> [s] -> DefaultMap (SequenceMap s) (RouteMap a) -> RouteResult a seqr p k (DefaultMap m d) = case lookupSequence k m of [] -> mayber p d l -> dynr p l