Copyright | (c) 2015 2016 2017 2018 Athan Clark |
---|---|
License | BSD-style |
Maintainer | athan.clark@gmail.com |
Stability | experimental |
Portability | GHC |
Safe Haskell | Safe-Inferred |
Language | Haskell2010 |
This module exports most of what you'll need for sophisticated routing -
all the tools from wai-middleware-verbs
(routing for the incoming HTTP method) and
wai-middleware-content-type
(routing for the incoming Accept header, and implied file extension),
WAI itself, and
wai-transformers - some simple
type aliases wrapped around WAI's Application
and Middleware
types, allowing us
to embed monad transformer stacks for our applications.
To match a route, you have a few options - you can match against a string literal, a regular expression (via regex-compat), or an attoparsec parser. This list will most likely grow in the future, depending on demand.
There is also support for embedding security layers in your routes, in the same
nested manner. By "tagging" a set of routes with an authorization role (with auth
),
you populate a list of roles breached during any request. The function argument to
routeAuth
guards a Request to pass or fail at the high level, while auth
lets
you create your authorization boundaries on a case-by-case basis. Both allow
you to tap into the monad transformer stack for logging, STRefs, database queries,
etc.
Synopsis
- match :: Monad m => Match xs' xs childContent resultContent => UrlChunks xs -> childContent -> RouterT resultContent sec m ()
- matchHere :: Monad m => childContent -> RouterT childContent sec m ()
- matchAny :: Monad m => childContent -> RouterT childContent sec m ()
- matchGroup :: Monad m => MatchGroup xs' xs childContent resultContent childSec resultSec => UrlChunks xs -> RouterT childContent childSec m () -> RouterT resultContent resultSec m ()
- auth :: Monad m => sec -> AuthScope -> RouterT content (SecurityToken sec) m ()
- route :: MonadIO m => RouterT (MiddlewareT m) sec m a -> MiddlewareT m
- routeAuth :: MonadIO m => MonadThrow m => (Request -> [sec] -> m ()) -> RouterT (MiddlewareT m) (SecurityToken sec) m a -> MiddlewareT m
- extractMatch :: MonadIO m => [Text] -> RouterT r sec m a -> m (Maybe r)
- extractMatchAny :: MonadIO m => [Text] -> RouterT r sec m a -> m (Maybe r)
- extractAuthSym :: MonadIO m => [Text] -> RouterT x (SecurityToken sec) m a -> m [sec]
- extractAuth :: MonadIO m => MonadThrow m => (Request -> [sec] -> m ()) -> Request -> RouterT x (SecurityToken sec) m a -> m ()
- extractNearestVia :: MonadIO m => [Text] -> (RouterT r sec m a -> m (RootedPredTrie Text r)) -> RouterT r sec m a -> m (Maybe r)
- data SecurityToken s = SecurityToken {
- securityToken :: !s
- securityScope :: !AuthScope
- data AuthScope
- type Match xs' xs childContent resultContent = (xs' ~ CatMaybes xs, Singleton (UrlChunks xs) childContent (RootedPredTrie Text resultContent), ArityTypeListIso childContent xs' resultContent)
- type MatchGroup xs' xs childContent resultContent childSec resultSec = (ExtrudeSoundly xs' xs childContent resultContent, ExtrudeSoundly xs' xs childSec resultSec)
- class ToUrlChunks a (xs :: [Maybe *]) | a -> xs where
- toUrlChunks :: a -> UrlChunks xs
- type UrlChunks = PathChunks Text
- type EitherUrlChunk = PathChunk Text
- o_ :: UrlChunks '[]
- origin_ :: UrlChunks '[]
- l_ :: Text -> EitherUrlChunk 'Nothing
- literal_ :: Text -> EitherUrlChunk 'Nothing
- f_ :: Text -> EitherUrlChunk ('Just Text)
- file_ :: Text -> EitherUrlChunk ('Just Text)
- p_ :: Text -> Parser r -> EitherUrlChunk ('Just r)
- parse_ :: Text -> Parser r -> EitherUrlChunk ('Just r)
- r_ :: Text -> Regex -> EitherUrlChunk ('Just [String])
- regex_ :: Text -> Regex -> EitherUrlChunk ('Just [String])
- pred_ :: Text -> (Text -> Maybe r) -> EitherUrlChunk ('Just r)
- (</>) :: EitherUrlChunk mx -> UrlChunks xs -> UrlChunks (mx ': xs)
- type ActionT urlbase m a = VerbListenerT (FileExtListenerT urlbase m a) m a
- type ExtrudeSoundly xs' xs c r = (xs' ~ CatMaybes xs, ArityTypeListIso c xs' r, Extrude (UrlChunks xs) (RootedPredTrie Text c) (RootedPredTrie Text r))
- newtype RouterT x sec m a = RouterT {
- runRouterT :: StateT (Tries x sec) m a
- data Tries x s = Tries {
- trieContent :: !(RootedPredTrie Text x)
- trieCatchAll :: !(RootedPredTrie Text x)
- trieSecurity :: !(RootedPredTrie Text s)
- execRouterT :: Monad m => RouterT x sec m a -> m (Tries x sec)
- action :: MonadBaseControl IO m stM => Extractable stM => ActionT urlbase m () -> MiddlewareT m
- module Network.Wai.Middleware.Verbs
- fileExtsToMiddleware :: forall m (stM :: Type -> Type) urlbase a. (MonadBaseControl IO m stM, Extractable stM) => FileExtListenerT urlbase m a -> MiddlewareT m
- lookupFileExt :: Maybe AcceptHeader -> Maybe FileExt -> FileExtMap -> Maybe Response
- blazeOnly :: Html -> Status -> ResponseHeaders -> Response
- blaze :: forall (m :: Type -> Type) urlbase. Monad m => Html -> FileExtListenerT urlbase m ()
- jsonOnly :: ToJSON j => j -> Status -> ResponseHeaders -> Response
- json :: forall j (m :: Type -> Type) urlbase. (ToJSON j, Monad m) => j -> FileExtListenerT urlbase m ()
- bytestringOnly :: ByteString -> Status -> ResponseHeaders -> Response
- bytestring :: forall (m :: Type -> Type) urlbase. Monad m => FileExt -> ByteString -> FileExtListenerT urlbase m ()
- cassiusOnly :: Css -> Status -> ResponseHeaders -> Response
- cassius :: forall (m :: Type -> Type) urlbase. Monad m => Css -> FileExtListenerT urlbase m ()
- clayOnly :: Config -> [App] -> Css -> Status -> ResponseHeaders -> Response
- clay :: forall (m :: Type -> Type) urlbase. Monad m => Config -> [App] -> Css -> FileExtListenerT urlbase m ()
- juliusOnly :: Javascript -> Status -> ResponseHeaders -> Response
- julius :: forall (m :: Type -> Type) urlbase. Monad m => Javascript -> FileExtListenerT urlbase m ()
- lucidOnly :: Monad m => HtmlT m () -> m (Status -> ResponseHeaders -> Response)
- lucid :: forall (m :: Type -> Type) urlbase. Monad m => HtmlT m () -> FileExtListenerT urlbase m ()
- luciusOnly :: Css -> Status -> ResponseHeaders -> Response
- lucius :: forall (m :: Type -> Type) urlbase. Monad m => Css -> FileExtListenerT urlbase m ()
- textOnly :: Text -> Status -> ResponseHeaders -> Response
- text :: forall (m :: Type -> Type) urlbase. Monad m => Text -> FileExtListenerT urlbase m ()
- invalidEncoding :: forall (m :: Type -> Type) urlbase. Monad m => ResponseVia -> FileExtListenerT urlbase m ()
- possibleFileExts :: [FileExt] -> AcceptHeader -> [FileExt]
- mapFileExtMap :: forall (m :: Type -> Type) urlbase a. Monad m => (FileExtMap -> FileExtMap) -> FileExtListenerT urlbase m a -> FileExtListenerT urlbase m a
- execFileExtListenerT :: Monad m => FileExtListenerT urlbase m a -> Maybe (Status -> Maybe Integer -> IO ()) -> m FileExtMap
- getLogger :: forall (m :: Type -> Type) urlbase. Monad m => FileExtListenerT urlbase m (Status -> Maybe Integer -> IO ())
- overFileExts :: forall (m :: Type -> Type) urlbase a. Monad m => [FileExt] -> (ResponseVia -> ResponseVia) -> FileExtListenerT urlbase m a -> FileExtListenerT urlbase m a
- mapHeaders :: (ResponseHeaders -> ResponseHeaders) -> ResponseVia -> ResponseVia
- mapStatus :: (Status -> Status) -> ResponseVia -> ResponseVia
- runResponseVia :: ResponseVia -> Response
- toExt :: (Text, Text) -> Maybe FileExt
- getFileExt :: [Text] -> Maybe FileExt
- data FileExt
- data ResponseVia = ResponseVia !a !Status !ResponseHeaders !(a -> Status -> ResponseHeaders -> Response)
- type FileExtMap = HashMap FileExt ResponseVia
- newtype FileExtListenerT urlbase (m :: Type -> Type) a = FileExtListenerT {
- runFileExtListenerT :: ReaderT (Status -> Maybe Integer -> IO ()) (StateT FileExtMap m) a
- type AcceptHeader = ByteString
Router Construction
:: Monad m | |
=> Match xs' xs childContent resultContent | |
=> UrlChunks xs | Predicative path to match against |
-> childContent | The response to send |
-> RouterT resultContent sec m () |
Embed a MiddlewareT
into a set of routes via a matching string. You should
expect the match to create arity in your handler - the childContent
variable.
The arity of childContent
may grow or shrink, depending on the heterogeneous
list created from the list of parsers, regular expressions, or arbitrary predicates
in the order written - so something like:
match (p_ "double-parser" double </> o_) handler
...then handler
must have arity Double ->
. If this
route was at the top level, then the total arity must be Double -> MiddlewareT m
.
Generally, if the routes you are building get grouped
by a predicate with matchGroup
,
then we would need another level of arity before the Double
.
Create a handle for the current route - an alias for h -> match o_ h
.
Match against any route, as a last resort against all failing matches -
use this for a catch-all at some level in their routes, something
like a not-found 404
page is useful.
:: Monad m | |
=> MatchGroup xs' xs childContent resultContent childSec resultSec | |
=> UrlChunks xs | Predicative path to match against |
-> RouterT childContent childSec m () | Child routes to nest |
-> RouterT resultContent resultSec m () |
Prepends a common route to an existing set of routes. You should note that doing this with a parser or regular expression will necessitate the existing arity in the handlers before the progam can compile.
:: Monad m | |
=> sec | Your security token |
-> AuthScope | |
-> RouterT content (SecurityToken sec) m () |
Sets the security role and error handler for a set of routes, optionally including its parent route.
Routing Middleware
:: MonadIO m | |
=> RouterT (MiddlewareT m) sec m a | The Router |
-> MiddlewareT m |
Use this function to run your RouterT
into a MiddlewareT
;
making your router executable in WAI. Note that this only
responds with content, and doesn't protect your routes with
your calls to auth
; to protect routes, postcompose this
with routeAuth
:
route routes . routeAuth routes
:: MonadIO m | |
=> MonadThrow m | |
=> (Request -> [sec] -> m ()) | authorization method |
-> RouterT (MiddlewareT m) (SecurityToken sec) m a | The Router |
-> MiddlewareT m |
Precise Route Extraction
Extracts only the normal match
, matchGroup
and matchHere
routes.
Extracts only the matchAny
responses; something like the greatest-lower-bound.
:: MonadIO m | |
=> [Text] | The path to match against |
-> RouterT x (SecurityToken sec) m a | The Router |
-> m [sec] |
Find the security tokens / authorization roles affiliated with a request for a set of routes.
:: MonadIO m | |
=> MonadThrow m | |
=> (Request -> [sec] -> m ()) | authorization method |
-> Request | |
-> RouterT x (SecurityToken sec) m a | |
-> m () |
Extracts only the security handling logic, and turns it into a guard.
:: MonadIO m | |
=> [Text] | The path to match against |
-> (RouterT r sec m a -> m (RootedPredTrie Text r)) | |
-> RouterT r sec m a | |
-> m (Maybe r) |
Given a way to draw out a special-purpose trie from our route set, route to the responses based on a furthest-route-reached method, or like a greatest-lower-bound.
Metadata
data SecurityToken s Source #
Use a custom security token type and an AuthScope
to define
where and what kind of security should take place.
SecurityToken | |
|
Instances
Show s => Show (SecurityToken s) Source # | |
Defined in Web.Routes.Nested showsPrec :: Int -> SecurityToken s -> ShowS # show :: SecurityToken s -> String # showList :: [SecurityToken s] -> ShowS # |
Designate the scope of security to the set of routes - either only the adjacent routes, or the adjacent and the parent container node (root node if not declared).
type Match xs' xs childContent resultContent = (xs' ~ CatMaybes xs, Singleton (UrlChunks xs) childContent (RootedPredTrie Text resultContent), ArityTypeListIso childContent xs' resultContent) Source #
The constraints necessary for match
.
type MatchGroup xs' xs childContent resultContent childSec resultSec = (ExtrudeSoundly xs' xs childContent resultContent, ExtrudeSoundly xs' xs childSec resultSec) Source #
The constraints necessary for matchGroup
.
Re-Exports
class ToUrlChunks a (xs :: [Maybe *]) | a -> xs where Source #
toUrlChunks :: a -> UrlChunks xs Source #
type UrlChunks = PathChunks Text Source #
Container when defining route paths
type EitherUrlChunk = PathChunk Text Source #
Constrained to AttoParsec, Regex-Compat and T.Text
file_ :: Text -> EitherUrlChunk ('Just Text) Source #
Removes file extension from the matchedhttp:/hackage.haskell.orgpackage/nested-routes route
parse_ :: Text -> Parser r -> EitherUrlChunk ('Just r) Source #
Match against a Parsed chunk, with attoparsec.
regex_ :: Text -> Regex -> EitherUrlChunk ('Just [String]) Source #
Match against a Regular expression chunk, with regex-compat.
pred_ :: Text -> (Text -> Maybe r) -> EitherUrlChunk ('Just r) Source #
Match with a predicate against the url chunk directly.
(</>) :: EitherUrlChunk mx -> UrlChunks xs -> UrlChunks (mx ': xs) infixr 9 Source #
Prefix a routable path by more predicative lookup data.
type ActionT urlbase m a = VerbListenerT (FileExtListenerT urlbase m a) m a Source #
The type of "content" builders; using the wai-middleware-verbs and wai-middleware-content-type packages.
type ExtrudeSoundly xs' xs c r = (xs' ~ CatMaybes xs, ArityTypeListIso c xs' r, Extrude (UrlChunks xs) (RootedPredTrie Text c) (RootedPredTrie Text r)) Source #
Soundness constraint showing that a function's arity can be represented as a type-level list.
newtype RouterT x sec m a Source #
The (syntactic) monad for building a router with functions like
"Web.Routes.Nested.match".
it should have a shape of RouterT (MiddlewareT m) (SecurityToken s) m a
when used with "Web.Routes.Nested.route".
RouterT | |
|
Instances
MonadTrans (RouterT x sec) Source # | |
Defined in Web.Routes.Nested.Types | |
Monad m => MonadState (Tries x sec) (RouterT x sec m) Source # | |
MonadIO m => MonadIO (RouterT x sec m) Source # | |
Defined in Web.Routes.Nested.Types | |
Monad m => Applicative (RouterT x sec m) Source # | |
Defined in Web.Routes.Nested.Types pure :: a -> RouterT x sec m a # (<*>) :: RouterT x sec m (a -> b) -> RouterT x sec m a -> RouterT x sec m b # liftA2 :: (a -> b -> c) -> RouterT x sec m a -> RouterT x sec m b -> RouterT x sec m c # (*>) :: RouterT x sec m a -> RouterT x sec m b -> RouterT x sec m b # (<*) :: RouterT x sec m a -> RouterT x sec m b -> RouterT x sec m a # | |
Functor m => Functor (RouterT x sec m) Source # | |
Monad m => Monad (RouterT x sec m) Source # | |
The internal data structure built during route declaration.
Tries | |
|
execRouterT :: Monad m => RouterT x sec m a -> m (Tries x sec) Source #
Run the monad, only getting the built state and throwing away a
.
action :: MonadBaseControl IO m stM => Extractable stM => ActionT urlbase m () -> MiddlewareT m Source #
Run the content builder into a middleware that responds when the content
is satisfiable (i.e. Accept
headers are O.K., etc.)
module Network.Wai.Middleware.Verbs
fileExtsToMiddleware :: forall m (stM :: Type -> Type) urlbase a. (MonadBaseControl IO m stM, Extractable stM) => FileExtListenerT urlbase m a -> MiddlewareT m #
lookupFileExt :: Maybe AcceptHeader -> Maybe FileExt -> FileExtMap -> Maybe Response #
Given an HTTP Accept
header and a content type to base lookups off of, and
a map of responses, find a response.
json :: forall j (m :: Type -> Type) urlbase. (ToJSON j, Monad m) => j -> FileExtListenerT urlbase m () #
bytestringOnly :: ByteString -> Status -> ResponseHeaders -> Response #
The exact same thing as Network.Wai.responseLBS
.
bytestring :: forall (m :: Type -> Type) urlbase. Monad m => FileExt -> ByteString -> FileExtListenerT urlbase m () #
cassiusOnly :: Css -> Status -> ResponseHeaders -> Response #
clay :: forall (m :: Type -> Type) urlbase. Monad m => Config -> [App] -> Css -> FileExtListenerT urlbase m () #
juliusOnly :: Javascript -> Status -> ResponseHeaders -> Response #
julius :: forall (m :: Type -> Type) urlbase. Monad m => Javascript -> FileExtListenerT urlbase m () #
lucid :: forall (m :: Type -> Type) urlbase. Monad m => HtmlT m () -> FileExtListenerT urlbase m () #
luciusOnly :: Css -> Status -> ResponseHeaders -> Response #
invalidEncoding :: forall (m :: Type -> Type) urlbase. Monad m => ResponseVia -> FileExtListenerT urlbase m () #
Use this combinator as the last one, as a "catch-all":
myApp = do text "foo" invalidEncoding myErrorHandler -- handles all except text/plain
possibleFileExts :: [FileExt] -> AcceptHeader -> [FileExt] #
Takes an Accept
header and returns the other
file types handleable, in order of prescedence.
mapFileExtMap :: forall (m :: Type -> Type) urlbase a. Monad m => (FileExtMap -> FileExtMap) -> FileExtListenerT urlbase m a -> FileExtListenerT urlbase m a #
execFileExtListenerT :: Monad m => FileExtListenerT urlbase m a -> Maybe (Status -> Maybe Integer -> IO ()) -> m FileExtMap #
getLogger :: forall (m :: Type -> Type) urlbase. Monad m => FileExtListenerT urlbase m (Status -> Maybe Integer -> IO ()) #
overFileExts :: forall (m :: Type -> Type) urlbase a. Monad m => [FileExt] -> (ResponseVia -> ResponseVia) -> FileExtListenerT urlbase m a -> FileExtListenerT urlbase m a #
mapHeaders :: (ResponseHeaders -> ResponseHeaders) -> ResponseVia -> ResponseVia #
mapStatus :: (Status -> Status) -> ResponseVia -> ResponseVia #
runResponseVia :: ResponseVia -> Response #
toExt :: (Text, Text) -> Maybe FileExt #
matches a file extension (including it's prefix dot - .html
for example)
to a known one.
Supported file extensions
Instances
data ResponseVia #
ResponseVia !a !Status !ResponseHeaders !(a -> Status -> ResponseHeaders -> Response) |
Instances
Monad m => MonadState FileExtMap (FileExtListenerT urlbase m) | |
Defined in Network.Wai.Middleware.ContentType.Types get :: FileExtListenerT urlbase m FileExtMap # put :: FileExtMap -> FileExtListenerT urlbase m () # state :: (FileExtMap -> (a, FileExtMap)) -> FileExtListenerT urlbase m a # | |
MonadBaseControl b m stM => MonadBaseControl b (FileExtListenerT urlbase m) (Compose stM ((,) FileExtMap)) | |
Defined in Network.Wai.Middleware.ContentType.Types liftBaseWith :: (RunInBase (FileExtListenerT urlbase m) b (Compose stM ((,) FileExtMap)) -> b a) -> FileExtListenerT urlbase m a # restoreM :: Compose stM ((,) FileExtMap) a -> FileExtListenerT urlbase m a # | |
MonadTransControl (FileExtListenerT urlbase) ((,) FileExtMap) | |
Defined in Network.Wai.Middleware.ContentType.Types liftWith :: Monad m => (Run (FileExtListenerT urlbase) ((,) FileExtMap) -> m a) -> FileExtListenerT urlbase m a # restoreT :: Monad m => m (FileExtMap, a) -> FileExtListenerT urlbase m a # |
type FileExtMap = HashMap FileExt ResponseVia #
newtype FileExtListenerT urlbase (m :: Type -> Type) a #
The monad for our DSL - when using the combinators, our result will be this type:
myListener :: FileExtListenerT base (MiddlewareT m) m () myListener = do text "Text!" json ("Json!" :: T.Text)
FileExtListenerT | |
|
Instances
type AcceptHeader = ByteString #