module Web.Wheb.Routes
(
(</>)
, grabInt
, grabText
, pT
, pS
, patRoute
, compilePat
, rootPat
, getParam
, matchUrl
, generateUrl
, findUrlMatch
, findSocketMatch
, findSiteMatch
, testUrlParser
) where
import Data.Monoid ((<>))
import qualified Data.Text.Lazy as T (fromStrict, null, pack, Text, toStrict)
import Data.Text.Lazy.Read (decimal, Reader)
import Data.Typeable (cast, Typeable)
import Network.HTTP.Types.Method (StdMethod)
import Network.HTTP.Types.URI (decodePathSegments, encodePathSegments)
import Web.Routes (runSite)
import Web.Wheb.Types (ChunkType(..), ParsedChunk(..), Route(Route), RouteParamList,
UrlBuildError(NoParam, ParamTypeMismatch), UrlParser(UrlParser),
UrlPat(Chunk, Composed, FuncChunk), WhebHandlerT, SocketRoute(SocketRoute),
PackedSite(..), WhebSocket)
import Web.Wheb.Utils (builderToText, lazyTextToSBS, spack)
patRoute :: (Maybe T.Text) ->
StdMethod ->
UrlPat ->
WhebHandlerT g s m ->
Route g s m
patRoute n m p = Route n (==m) (compilePat p)
compilePat :: UrlPat -> UrlParser
compilePat (Composed a) = UrlParser (matchPat a) (buildPat a)
compilePat a = UrlParser (matchPat [a]) (buildPat [a])
rootPat :: UrlPat
rootPat = Chunk $ T.pack ""
(</>) :: UrlPat -> UrlPat -> UrlPat
(Composed a) </> (Composed b) = Composed (a ++ b)
a </> (Composed b) = Composed (a:b)
(Composed a) </> b = Composed (a ++ [b])
a </> b = Composed [a, b]
grabInt :: T.Text -> UrlPat
grabInt key = FuncChunk key f IntChunk
where rInt = decimal :: Reader Int
f = ((either (const Nothing) (Just . MkChunk . fst)) . rInt)
grabText :: T.Text -> UrlPat
grabText key = FuncChunk key (Just . MkChunk) TextChunk
pT :: T.Text -> UrlPat
pT = Chunk
pS :: String -> UrlPat
pS = pT . T.pack
getParam :: Typeable a => T.Text -> RouteParamList -> Maybe a
getParam k l = (lookup k l) >>= unwrap
where unwrap :: Typeable a => ParsedChunk -> Maybe a
unwrap (MkChunk a) = cast a
matchUrl :: [T.Text] -> UrlParser -> Maybe RouteParamList
matchUrl url (UrlParser f _) = f url
generateUrl :: UrlParser -> RouteParamList -> Either UrlBuildError T.Text
generateUrl (UrlParser _ f) = f
findUrlMatch :: StdMethod ->
[T.Text] ->
[Route g s m] ->
Maybe (WhebHandlerT g s m, RouteParamList)
findUrlMatch _ _ [] = Nothing
findUrlMatch rmtd path ((Route _ methodMatch (UrlParser f _) h):rs)
| not (methodMatch rmtd) = findUrlMatch rmtd path rs
| otherwise = case f path of
Just params -> Just (h, params)
Nothing -> findUrlMatch rmtd path rs
findSocketMatch :: [T.Text] -> [SocketRoute g s m] -> Maybe (WhebSocket g s m, RouteParamList)
findSocketMatch _ [] = Nothing
findSocketMatch path ((SocketRoute (UrlParser f _) h):rs) =
case f path of
Just params -> Just (h, params)
Nothing -> findSocketMatch path rs
findSiteMatch :: [PackedSite g s m] ->
[T.Text] ->
Maybe (WhebHandlerT g s m)
findSiteMatch [] _ = Nothing
findSiteMatch ((PackedSite t site):sites) cs =
either (const (findSiteMatch sites cs)) Just $
runSite (T.toStrict t) site (map T.toStrict cs)
testUrlParser :: UrlParser -> RouteParamList -> Bool
testUrlParser up rpl =
case generateUrl up rpl of
Left _ -> False
Right t -> case (matchUrl (fmap T.fromStrict $ decodeUrl t) up) of
Just params -> either (const False) (==t) (generateUrl up params)
Nothing -> False
where decodeUrl = decodePathSegments . lazyTextToSBS
matchPat :: [UrlPat] -> [T.Text] -> Maybe RouteParamList
matchPat chunks [] = matchPat chunks [T.pack ""]
matchPat chunks t = parse t chunks []
where parse [] [] params = Just params
parse [] c params = Nothing
parse (u:[]) [] params | T.null u = Just params
| otherwise = Nothing
parse (u:us) [] _ = Nothing
parse (u:us) ((Chunk c):cs) params | T.null c = parse (u:us) cs params
| u == c = parse us cs params
| otherwise = Nothing
parse (u:us) ((FuncChunk k f _):cs) params = do
val <- f u
parse us cs ((k, val):params)
parse us ((Composed xs):cs) params = parse us (xs ++ cs) params
buildPat :: [UrlPat] -> RouteParamList -> Either UrlBuildError T.Text
buildPat pats params = fmap addSlashes $ build [] pats
where build acc [] = Right acc
build acc ((Chunk c):[]) = build (acc <> [c]) []
build acc ((Chunk c):cs) | T.null c = build acc cs
| otherwise = build (acc <> [c]) cs
build acc ((Composed xs):cs) = build acc (xs <> cs)
build acc ((FuncChunk k _ t):cs) =
case (showParam t k params) of
(Right v) -> build (acc <> [v]) cs
(Left err) -> Left err
addSlashes [] = T.pack "/"
addSlashes list = builderToText $
encodePathSegments (fmap T.toStrict list)
showParam :: ChunkType -> T.Text -> RouteParamList -> Either UrlBuildError T.Text
showParam chunkType k l =
case (lookup k l) of
Just (MkChunk v) -> case chunkType of
IntChunk -> toEither $ fmap spack (cast v :: Maybe Int)
TextChunk -> toEither (cast v :: Maybe T.Text)
Nothing -> Left NoParam
where toEither v = case v of
Just b -> Right b
Nothing -> Left $ ParamTypeMismatch k