{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE BangPatterns #-} -- | This module contains helpers for use "Yesod.Routes.Dispatch" with -- "Network.Wai". -- -- This 'Middleware' uses first 'D.Piece' in path to route @HTTP@ method. -- 'D.Static' means concrete method. 'D.Dynamic' means any method. module Network.Wai.Middleware.Route ( -- * Routing rules Rule(..), mkRoutes, mkRoutes', mkRoute, -- * Middleware dispatch ) where import Control.Applicative ((<$>), (<*>)) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8) import Network.Wai (Application, requestMethod, pathInfo) import qualified Yesod.Routes.Dispatch as D -- | Rule for route. Rules without single quotes (@'@) means fixed length -- paths. And vice versa, rules with single quotes (@'@) means paths with -- variable lengh -- -- Paths converts to 'D.Piece's by following rules: -- -- * Paths splits by slashes (@/@). -- -- * Text between slashes becomes 'D.Static' 'D.Piece'. The same thing -- happens with the text at the ends of paths. -- -- * Double (triple, etc.) slashes means becomes 'D.Dynamic' 'D.Piece's. -- The same thing happens with the slashes at the ends of paths. -- -- > "foo" -- > [Static "foo"] -- > -- > "foo/bar" -- > [Static "foo", Static "bar"] -- > -- > "foo//bar" -- > [Static "foo", Dynamic, Static "bar"] -- > -- > "/foo//bar/baz/" -- > [Dynamic, Static "foo", Dynamic, Static "bar", Static "baz", Dynamic] -- > data Rule = Get T.Text Application -- ^ @GET@, fixed length path | Post T.Text Application -- ^ @POST@, fixed length path | Head T.Text Application -- ^ @HEAD@, fixed length path | Put T.Text Application -- ^ @PUT@, fixed length path | Delete T.Text Application -- ^ @DELETE@, fixed length path | Trace T.Text Application -- ^ @TRACE@, fixed length path | Connect T.Text Application -- ^ @CONNECT@, fixed length path | Options T.Text Application -- ^ @OPTIONS@, fixed length path | Any T.Text Application -- ^ Any @HTTP@ method, fixed length path | Get' T.Text Application -- ^ @GET@, variable length path | Post' T.Text Application -- ^ @POST@, variable length path | Head' T.Text Application -- ^ @HEAD@, variable length path | Put' T.Text Application -- ^ @PUT@, variable length path | Delete' T.Text Application -- ^ @DELETE@, variable length path | Trace' T.Text Application -- ^ @TRACE@, variable length path | Connect' T.Text Application -- ^ @CONNECT@, variable length path | Options' T.Text Application -- ^ @OPTIONS@, variable length path | Any' T.Text Application -- ^ Any @HTTP@ method, variable length path | Gen Bool T.Text T.Text Application -- ^ Generic rule with path lenghts flag, @HTTP@ method and path -- | Make 'D.Route's from 'Rules'. -- -- Equivalent @map mkRoute@ mkRoutes :: [Rule] -- ^ Routing rules -> [D.Route Application] mkRoutes = map mkRoute {-# INLINE mkRoutes #-} -- | Make 'D.Dispatch's from 'Rules'. -- -- Equivalent @toDispatch . mkRoutes@ mkRoutes' :: [Rule] -> D.Dispatch Application mkRoutes' = D.toDispatch . mkRoutes -- | Make 'D.Route' from 'Rule'. 'D.rhPieces' of 'D.Route' will be -- prepended with 'D.Piece' with corresponding @HTTP@ method. -- 'D.Static' means concrete method. 'D.Dynamic' means any method. -- -- > mkRoute $ Get "foo/bar" app -- > Route [Static "foo", Static "bar"] False (const $ Just app) mkRoute :: Rule -> D.Route Application mkRoute (Get p a) = mkGenRoute (D.Static "GET") False p a mkRoute (Post p a) = mkGenRoute (D.Static "POST") False p a mkRoute (Head p a) = mkGenRoute (D.Static "HEAD") False p a mkRoute (Put p a) = mkGenRoute (D.Static "PUT") False p a mkRoute (Delete p a) = mkGenRoute (D.Static "DELETE") False p a mkRoute (Trace p a) = mkGenRoute (D.Static "TRACE") False p a mkRoute (Connect p a) = mkGenRoute (D.Static "CONNECT") False p a mkRoute (Options p a) = mkGenRoute (D.Static "OPTIONS") False p a mkRoute (Any p a) = mkGenRoute D.Dynamic False p a mkRoute (Get' p a) = mkGenRoute (D.Static "GET") True p a mkRoute (Post' p a) = mkGenRoute (D.Static "POST") True p a mkRoute (Head' p a) = mkGenRoute (D.Static "HEAD") True p a mkRoute (Put' p a) = mkGenRoute (D.Static "PUT") True p a mkRoute (Delete' p a) = mkGenRoute (D.Static "DELETE") True p a mkRoute (Trace' p a) = mkGenRoute (D.Static "TRACE") True p a mkRoute (Connect' p a) = mkGenRoute (D.Static "CONNECT") True p a mkRoute (Options' p a) = mkGenRoute (D.Static "OPTIONS") True p a mkRoute (Any' p a) = mkGenRoute D.Dynamic True p a mkRoute (Gen v m p a) = mkGenRoute (D.Static m) v p a {-# INLINE mkRoute #-} -- | Make generic route mkGenRoute :: D.Piece -- ^ Method piece. 'D.Dynamic' means any method. -> Bool -- ^ 'D.rhHasMulti' -> T.Text -- ^ Path pieces -> Application -- ^ Routed application -> D.Route Application mkGenRoute m hasMulti pieces = D.Route (m:mkPieces pieces) hasMulti . const . Just {-# INLINE mkGenRoute #-} -- | Make Pieces from path mkPieces :: T.Text -> [D.Piece] mkPieces = map chunk . protPath . T.split (=='/') where protPath [""] = [] protPath p = p chunk "" = D.Dynamic chunk c = D.Static c {-# INLINE mkPieces #-} ----------------------------------------------------------------------------- -- Middleware. ----------------------------------------------------------------------------- -- | Dispatch function. -- -- > rs :: Dispatch Application -- > rs = toDispatch . mkRoutes [ -- > Get "foo" fooGetApp -- > , Post "foo" fooPostApp -- > , Get "foo//bar" fooDynBarApp -- > -- > , Any "any" anyMethodApp -- > ] -- > -- > app :: Application -- > app = dispatch rs (error "Not dispatched") dispatch :: D.Dispatch Application -- ^ Dispatch function. -- Use 'D.toDispatch' and route helpers below. -> Application -- ^ Default (@404@) application. -> Application dispatch mappings defApp req = case mappings . needle $ req of Nothing -> defApp req (Just app) -> app req where needle = (:) <$> decodeUtf8 . requestMethod <*> pathInfo