wai-middleware-route-0.5.1: src/Network/Wai/Middleware/Route.hs
{-# 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