1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
|
{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances #-}
{-# LANGUAGE ExistentialQuantification #-}
{- | This module gives you a way to mount applications under sub-URIs.
For example:
> bugsApp, helpdeskApp, apiV1, apiV2, mainApp :: Application
>
> myApp :: Application
> myApp = mapUrls $
> mount "bugs" bugsApp
> <|> mount "helpdesk" helpdeskApp
> <|> mount "api"
> ( mount "v1" apiV1
> <|> mount "v2" apiV2
> )
> <|> mountRoot mainApp
-}
module Network.Wai.UrlMap (
UrlMap',
UrlMap,
mount',
mount,
mountRoot,
mapUrls
) where
import Control.Applicative
import Data.List
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.ByteString as B
import Network.HTTP.Types
import Network.Wai
type Path = [Text]
newtype UrlMap' a = UrlMap' { unUrlMap :: [(Path, a)] }
instance Functor UrlMap' where
fmap f (UrlMap' xs) = UrlMap' (fmap (\(p, a) -> (p, f a)) xs)
instance Applicative UrlMap' where
pure x = UrlMap' [([], x)]
(UrlMap' xs) <*> (UrlMap' ys) = UrlMap' [ (p, f y) |
(p, y) <- ys,
f <- map snd xs ]
instance Alternative UrlMap' where
empty = UrlMap' empty
(UrlMap' xs) <|> (UrlMap' ys) = UrlMap' (xs <|> ys)
type UrlMap = UrlMap' Application
-- | Mount an application under a given path. The ToApplication typeclass gives
-- you the option to pass either an 'Network.Wai.Application' or an 'UrlMap'
-- as the second argument.
mount' :: ToApplication a => Path -> a -> UrlMap
mount' prefix thing = UrlMap' [(prefix, toApplication thing)]
-- | A convenience function like mount', but for mounting things under a single
-- path segment.
mount :: ToApplication a => Text -> a -> UrlMap
mount prefix thing = mount' [prefix] thing
-- | Mount something at the root. Use this for the last application in the
-- block, to avoid 500 errors from none of the applications matching.
mountRoot :: ToApplication a => a -> UrlMap
mountRoot = mount' []
try :: Eq a
=> [a] -- ^ Path info of request
-> [([a], b)] -- ^ List of applications to match
-> Maybe ([a], b)
try xs tuples = foldl go Nothing tuples
where
go (Just x) _ = Just x
go _ (prefix, y) = stripPrefix prefix xs >>= \xs' -> return (xs', y)
class ToApplication a where
toApplication :: a -> Application
instance ToApplication Application where
toApplication = id
instance ToApplication UrlMap where
toApplication urlMap req sendResponse =
case try (pathInfo req) (unUrlMap urlMap) of
Just (newPath, app) ->
app (req { pathInfo = newPath
, rawPathInfo = makeRaw newPath
}) sendResponse
Nothing ->
sendResponse $ responseLBS
status404
[("content-type", "text/plain")]
"Not found\n"
where
makeRaw :: [Text] -> B.ByteString
makeRaw = ("/" `B.append`) . T.encodeUtf8 . T.intercalate "/"
mapUrls :: UrlMap -> Application
mapUrls = toApplication
|