packages feed

growler 0.3.2 → 0.4.0

raw patch · 5 files changed

+245/−73 lines, 5 filesdep ~basedep ~lensPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, lens

API changes (from Hackage documentation)

+ Web.Growler: AttoparsecError :: ParsingError -> DecodingError
+ Web.Growler: FromJSONError :: String -> DecodingError
+ Web.Growler: GrowlerConfig :: HandlerT m () -> (SomeException -> HandlerT m ()) -> GrowlerConfig m
+ Web.Growler: JsonError :: DecodingError -> JsonInputError
+ Web.Growler: RequestBodyExhausted :: JsonInputError
+ Web.Growler: ResponseState :: !Status -> !(HashMap (CI ByteString) [ByteString]) -> !BodySource -> ResponseState
+ Web.Growler: builder :: Monad m => Builder -> HandlerT m ()
+ Web.Growler: bytestring :: Monad m => ByteString -> HandlerT m ()
+ Web.Growler: data DecodingError :: *
+ Web.Growler: data GrowlerConfig m
+ Web.Growler: data JsonInputError
+ Web.Growler: defaultConfig :: MonadIO m => GrowlerConfig m
+ Web.Growler: growlerConfigErrorHandler :: GrowlerConfig m -> SomeException -> HandlerT m ()
+ Web.Growler: growlerConfigNotFoundHandler :: GrowlerConfig m -> HandlerT m ()
+ Web.Growler: lookupParam :: (Functor m, Monad m, Parsable a) => ByteString -> HandlerT m (Maybe a)
+ Web.Growler: param :: (Functor m, Monad m, Parsable a) => ByteString -> HandlerT m a
+ Web.Growler: raise :: Monad m => ByteString -> HandlerT m ()
+ Web.Growler: responseStateBodySource :: ResponseState -> !BodySource
+ Web.Growler: responseStateHeaders :: ResponseState -> !(HashMap (CI ByteString) [ByteString])
+ Web.Growler: responseStateStatus :: ResponseState -> !Status
+ Web.Growler.Handler: builder :: Monad m => Builder -> HandlerT m ()
+ Web.Growler.Handler: bytestring :: Monad m => ByteString -> HandlerT m ()
+ Web.Growler.Handler: lookupParam :: (Functor m, Monad m, Parsable a) => ByteString -> HandlerT m (Maybe a)
+ Web.Growler.Handler: param :: (Functor m, Monad m, Parsable a) => ByteString -> HandlerT m a
+ Web.Growler.Handler: raise :: Monad m => ByteString -> HandlerT m ()
+ Web.Growler.Router: internalServerError :: Monad m => HandlerT m ()
+ Web.Growler.Types: GrowlerConfig :: HandlerT m () -> (SomeException -> HandlerT m ()) -> GrowlerConfig m
+ Web.Growler.Types: JsonError :: DecodingError -> JsonInputError
+ Web.Growler.Types: RequestBodyExhausted :: JsonInputError
+ Web.Growler.Types: RoutePatternResult :: !Text -> !Request -> !MatchResult -> RoutePatternResult
+ Web.Growler.Types: data GrowlerConfig m
+ Web.Growler.Types: data JsonInputError
+ Web.Growler.Types: data RoutePatternResult
+ Web.Growler.Types: growlerConfigErrorHandler :: GrowlerConfig m -> SomeException -> HandlerT m ()
+ Web.Growler.Types: growlerConfigNotFoundHandler :: GrowlerConfig m -> HandlerT m ()
+ Web.Growler.Types: instance Eq JsonInputError
+ Web.Growler.Types: instance Show JsonInputError
+ Web.Growler.Types: routePatternResultMatchResult :: RoutePatternResult -> !MatchResult
+ Web.Growler.Types: routePatternResultName :: RoutePatternResult -> !Text
+ Web.Growler.Types: routePatternResultRequest :: RoutePatternResult -> !Request
- Web.Growler: FileSource :: !(FilePath, Maybe FilePart) -> BodySource
+ Web.Growler: FileSource :: !FilePath -> !(Maybe FilePart) -> BodySource
- Web.Growler: RoutePattern :: (Request -> (Text, Request, MatchResult)) -> RoutePattern
+ Web.Growler: RoutePattern :: (Request -> RoutePatternResult) -> RoutePattern
- Web.Growler: growl :: MonadIO m => (forall a. m a -> IO a) -> HandlerT m () -> GrowlerT m () -> IO ()
+ Web.Growler: growl :: MonadIO m => (forall a. m a -> IO a) -> GrowlerConfig m -> GrowlerT m () -> IO ()
- Web.Growler: growler :: MonadIO m => (forall a. m a -> IO a) -> HandlerT m () -> GrowlerT m () -> IO Application
+ Web.Growler: growler :: MonadIO m => (forall a. m a -> IO a) -> GrowlerConfig m -> GrowlerT m () -> IO Application
- Web.Growler: jsonData :: (FromJSON a, MonadIO m) => HandlerT m (Either String a)
+ Web.Growler: jsonData :: (FromJSON a, MonadIO m) => HandlerT m (Either JsonInputError a)
- Web.Growler: notFound :: MonadIO m => HandlerT m ()
+ Web.Growler: notFound :: Monad m => HandlerT m ()
- Web.Growler: raw :: Monad m => ByteString -> HandlerT m ()
+ Web.Growler: raw :: MonadIO m => (IO ByteString -> (ByteString -> IO ()) -> IO ()) -> Response -> HandlerT m ()
- Web.Growler: runRoutePattern :: RoutePattern -> Request -> (Text, Request, MatchResult)
+ Web.Growler: runRoutePattern :: RoutePattern -> Request -> RoutePatternResult
- Web.Growler.Handler: jsonData :: (FromJSON a, MonadIO m) => HandlerT m (Either String a)
+ Web.Growler.Handler: jsonData :: (FromJSON a, MonadIO m) => HandlerT m (Either JsonInputError a)
- Web.Growler.Handler: raw :: Monad m => ByteString -> HandlerT m ()
+ Web.Growler.Handler: raw :: MonadIO m => (IO ByteString -> (ByteString -> IO ()) -> IO ()) -> Response -> HandlerT m ()
- Web.Growler.Router: RoutePattern :: (Request -> (Text, Request, MatchResult)) -> RoutePattern
+ Web.Growler.Router: RoutePattern :: (Request -> RoutePatternResult) -> RoutePattern
- Web.Growler.Router: notFound :: MonadIO m => HandlerT m ()
+ Web.Growler.Router: notFound :: Monad m => HandlerT m ()
- Web.Growler.Router: runRoutePattern :: RoutePattern -> Request -> (Text, Request, MatchResult)
+ Web.Growler.Router: runRoutePattern :: RoutePattern -> Request -> RoutePatternResult
- Web.Growler.Types: FileSource :: !(FilePath, Maybe FilePart) -> BodySource
+ Web.Growler.Types: FileSource :: !FilePath -> !(Maybe FilePart) -> BodySource
- Web.Growler.Types: RoutePattern :: (Request -> (Text, Request, MatchResult)) -> RoutePattern
+ Web.Growler.Types: RoutePattern :: (Request -> RoutePatternResult) -> RoutePattern
- Web.Growler.Types: runRoutePattern :: RoutePattern -> Request -> (Text, Request, MatchResult)
+ Web.Growler.Types: runRoutePattern :: RoutePattern -> Request -> RoutePatternResult

Files

growler.cabal view
@@ -2,11 +2,24 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                growler-version:             0.3.2+version:             0.4.0 synopsis:            A revised version of the scotty library that attempts to be simpler and more performant. description:         Growler provides a very similar interface to scotty, with slight tweaks for performance and a few feature tradeoffs. Growler provides the ability to abort actions (handlers) with arbitrary responses, not just in the event of redirects or raising errors. Growler avoids coercing everything into lazy Text values and reading the whole request body into memory. It also eliminates the ability to abort the handler and have another handler handle the request instead (Scotty's 'next' function). 		     .                      API is still in flux, so use at your own risk. Pull requests / issues are welcome.+                     .+                     @+                       &#123;-&#35; LANGUAGE OverloadedStrings &#35;-&#125;+                       .+                       import Web.Growler+                       import Data.Monoid ((&#60;&#62;))+                       .+                       main = growl id defaultConfig $ do+                       &#32;&#32;get &#34;/&#34; $ text &#34;Hello, World!&#34;+                       &#32;&#32;get &#34;/:name&#34; $ do+                       &#32;&#32;&#32;&#32;name <- param &#34;name&#34;+                       &#32;&#32;&#32;&#32;text (&#34;Hello, &#34; &#60;&#62; name &#60;&#62; &#34;!&#34;)+                     @  homepage:            http://github.com/iand675/growler license:             MIT@@ -22,6 +35,26 @@ library   exposed-modules:   Web.Growler, Web.Growler.Handler, Web.Growler.Parsable, Web.Growler.Router, Web.Growler.Types   other-extensions:    OverloadedStrings, FlexibleContexts, FlexibleInstances, LambdaCase, RankNTypes, ScopedTypeVariables-  build-depends:       base >=4.7 && <4.8, lens >=4.4 && <5, mtl >=2.2 && <3, bytestring >=0.10 && <0.20, http-types >=0.8 && <1, text >=1.1 && <2, wai >=3.0 && <4, wai-extra >=3.0 && <4, regex-compat >=0.95 && <1, blaze-builder >=0.3 && <0.7, unordered-containers >=0.2 && <0.9, aeson, vector, case-insensitive, warp, pipes, pipes-aeson, pipes-wai, monad-control, either >= 4.3.1, transformers-base+  build-depends:       base >=4.6 && <5,+                       lens >=4.5 && <5,+                       mtl >=2.2 && <3,+                       bytestring >=0.10 && <0.20,+                       http-types >=0.8 && <1,+                       text >=1.1 && <2,+                       wai >=3.0 && <4,+                       wai-extra >=3.0 && <4,+                       regex-compat >=0.95 && <1,+                       blaze-builder >=0.3 && <0.7,+                       unordered-containers >=0.2 && <0.9,+                       aeson,+                       vector,+                       case-insensitive,+                       warp,+                       pipes,+                       pipes-aeson,+                       pipes-wai,+                       monad-control,+                       either >= 4.3.1,+                       transformers-base   hs-source-dirs:      src   default-language:    Haskell2010
src/Web/Growler.hs view
@@ -1,58 +1,92 @@ {-# LANGUAGE OverloadedStrings   #-} {-# LANGUAGE Rank2Types          #-} {-# LANGUAGE ScopedTypeVariables #-}+{-|++A Haskell web framework inspired by the Scotty framework, with an+eye towards performance, extensibility, and ease of use.++> {-# LANGUAGE OverloadedStrings #-}+> module Main where+> import Data.Monoid ((<>))+> import Web.Growler+>+> main = growl id defaultConfig $ do+>   get "/" $ text "Hello, World!"+>   get "/:name" $ do+>     name <- param "name"+>     text ("Hello, " <> name <> "!")++-} module Web.Growler ( -- ** Running a growler app   growl , growler+, defaultConfig+, GrowlerConfig (..) -- ** Routing , Growler , GrowlerT+, regex+, capture+, function+, literal+, mount+, handlerHook+, notFound+-- *** HTTP Methods , get , post , put , delete , patch , matchAny-, notFound+-- *** Primitives , addRoute-, regex-, capture-, function-, literal-, mount-, handlerHook -- ** Handlers , Handler , HandlerT+-- *** Primitive request functions+, request+, routePattern+, params+-- *** Primitive response functions+, file+, builder+, bytestring+, stream+, raw , currentResponse , abort-, status-, addHeader-, setHeader-, body-, html-, json-, file+-- *** Convenience functions+-- **** Request helpers+, lookupParam+, param , formData , headers , jsonData-, params-, raw+-- **** Response helpers+, status+, addHeader+, setHeader+, raise , redirect-, request-, stream , text-, routePattern+, html+, json+, JsonInputError (..)+, DecodingError (..) -- ** Parsable , Parsable (..) , readEither -- ** Internals+, body , BodySource (..)-, ResponseState+, ResponseState (..) , RoutePattern (..) ) where+import           Control.Exception         (catch) import           Control.Lens              hiding (get) import           Control.Monad.Identity import           Control.Monad.State       hiding (get, put)@@ -64,23 +98,41 @@ import           Network.HTTP.Types.Method import           Network.Wai import qualified Network.Wai.Handler.Warp  as Warp+import           Pipes.Aeson               (DecodingError (..)) import           Web.Growler.Handler import           Web.Growler.Parsable import           Web.Growler.Router import           Web.Growler.Types         hiding (status, headers, params, request, capture) -growl :: MonadIO m => (forall a. m a -> IO a) -> HandlerT m () -> GrowlerT m () -> IO ()+-- | The simple approach to starting up a web server+growl :: MonadIO m => (forall a. m a -> IO a) -- ^ A function to convert your base monad of choice into IO.+                   -> GrowlerConfig m       +                   -> GrowlerT m ()           -- ^ The router for all the other routes+                   -> IO () growl trans fb g = do   app <- growler trans fb g   putStrLn "Growling"   Warp.run 3000 app -growler :: MonadIO m => (forall a. m a -> IO a) -> HandlerT m () -> GrowlerT m () -> IO Application-growler trans fallback (GrowlerT m) = do+-- | For more complex needs, access to the actual WAI 'Application'. Useful for adding middleware.+growler :: MonadIO m => (forall a. m a -> IO a) -- ^ A function to convert your base monad of choice into IO.+                     -> GrowlerConfig m+                     -> GrowlerT m ()           -- ^ The router for all the other routes+                     -> IO Application+growler trans (GrowlerConfig nf er) (GrowlerT m) = do   result <- trans $ execStateT m []   return $ app (reverse result ^. vector)   where-    app rv req respond = trans (growlerRouter rv fallback req) >>= respond+    app rv req respond = catch (trans (growlerRouter rv nf req) >>= respond) $ \e -> do+      mr <- trans (runHandler initialState Nothing req [] (er e))+      let (ResponseState status' groupedHeaders body') = either id snd mr+      let headers = concatMap (\(k, vs) -> map (\v -> (k, v)) vs) $ HM.toList groupedHeaders+      respond $ case body' of+        FileSource fpath fpart    -> responseFile    status' headers fpath fpart+        BuilderSource b           -> responseBuilder status' headers b+        LBSSource lbs             -> responseLBS     status' headers lbs+        StreamSource sb           -> responseStream  status' headers sb+        RawSource f r'            -> responseRaw     f       r'  growlerRouter :: forall m. MonadIO m => V.Vector (StdMethod, RoutePattern, HandlerT m ()) -> HandlerT m () -> Request -> m Response growlerRouter rv fb r = do@@ -88,13 +140,17 @@   let (ResponseState status' groupedHeaders body') = either id snd rs   let headers = concatMap (\(k, vs) -> map (\v -> (k, v)) vs) $ HM.toList groupedHeaders   return $! case body' of-    FileSource (fpath, fpart) -> responseFile    status' headers fpath fpart+    FileSource fpath fpart    -> responseFile    status' headers fpath fpart     BuilderSource b           -> responseBuilder status' headers b     LBSSource lbs             -> responseLBS     status' headers lbs     StreamSource sb           -> responseStream  status' headers sb-    RawSource f r'            -> responseRaw     f      r'+    RawSource f r'            -> responseRaw     f       r'   where     processResponse (m, pat, respond) = case route r m pat of       Nothing -> Nothing       Just (patRep, ps) -> Just $ runHandler initialState (Just patRep) r ps respond +defaultConfig :: MonadIO m => GrowlerConfig m+defaultConfig = GrowlerConfig notFound $ \e -> do+  liftIO $ print e+  internalServerError
src/Web/Growler/Handler.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE Rank2Types        #-} {-# LANGUAGE OverloadedStrings #-} module Web.Growler.Handler where+import           Blaze.ByteString.Builder (Builder) import           Control.Applicative import           Control.Lens import           Control.Monad.RWS@@ -10,10 +11,10 @@ import qualified Control.Monad.State.Strict as ST import           Data.Aeson                hiding ((.=)) import qualified Data.ByteString.Char8     as C+import qualified Data.ByteString.Lazy.Char8 as L import           Data.CaseInsensitive import           Data.Maybe import qualified Data.HashMap.Strict       as HM-import qualified Data.ByteString.Lazy.Char8 as L import           Data.Text as T import           Data.Text.Encoding as T import           Data.Text.Lazy as TL@@ -22,10 +23,11 @@ import           Network.Wai import           Network.Wai.Parse hiding (Param) import           Network.HTTP.Types+import           Web.Growler.Parsable import           Web.Growler.Types hiding (status, request, params) import qualified Web.Growler.Types as L-import Pipes.Wai-import Pipes.Aeson+import           Pipes.Wai+import           Pipes.Aeson  initialState :: ResponseState initialState = ResponseState ok200 HM.empty (LBSSource "")@@ -33,83 +35,144 @@ currentResponse :: Monad m => HandlerT m ResponseState currentResponse = HandlerT State.get +-- | End the handler early with an arbitrary 'ResponseState'.  abort :: Monad m => ResponseState -> HandlerT m () abort rs = HandlerT $ lift $ left rs +-- | Set the response status code. status :: Monad m => Status -> HandlerT m () status v = HandlerT $ L.status .= v +-- | Add a header to the response. Header names are case-insensitive. addHeader :: Monad m => CI C.ByteString -> C.ByteString -> HandlerT m () addHeader k v = HandlerT (L.headers %= HM.insertWith (\_ v' -> v:v') k [v]) +-- | Set a response header. Overrides duplicate headers of the same name. setHeader :: Monad m => CI C.ByteString -> C.ByteString -> HandlerT m () setHeader k v = HandlerT (L.headers %= HM.insert k [v]) +-- | Set an arbitrary body source for the response. body :: Monad m => BodySource -> HandlerT m () body = HandlerT . (bodySource .=) +-- | Send a file as the response body.+file :: Monad m => FilePath       -- ^ The file to send+                -> Maybe FilePart -- ^ If 'Nothing', then send the whole file, otherwise, the part specified +                -> HandlerT m ()+file fpath fpart = HandlerT (bodySource .= FileSource fpath fpart)++-- | Set the response body to a ByteString 'Builder'. Sets no headers.+builder :: Monad m => Builder+                   -> HandlerT m ()+builder b = HandlerT (bodySource .= BuilderSource b)++-- | Set the response body to a lazy 'ByteString'. Sets no headers.+bytestring :: Monad m => L.ByteString -> HandlerT m ()+bytestring bs = HandlerT (bodySource .= LBSSource bs)++-- | Send a streaming response body. Sets no headers.+stream :: Monad m => StreamingBody -> HandlerT m ()+stream s = HandlerT (bodySource .= StreamSource s)++-- | Send raw output as the response body. Useful for e.g. websockets. See WAI's @responseRaw@ for more details.+raw :: MonadIO m => (IO C.ByteString -> (C.ByteString -> IO ()) -> IO ())+                 -> Response -- ^ Backup response when the WAI provider doesn't support upgrading (e.g. CGI)+                 -> HandlerT m ()+raw f r = HandlerT (bodySource .= RawSource f r)++-- | Send a value as JSON as the response body. Also sets the content type to application/json. json :: Monad m => ToJSON a => a -> HandlerT m () json x = do   body $ LBSSource $ encode x   addHeader "Content-Type" "application/json" -file :: Monad m => FilePath -> Maybe FilePart -> HandlerT m ()-file fpath fpart = HandlerT (bodySource .= FileSource (fpath, fpart))-+-- | Parse out the form parameters and the uploaded files. Consumes the request body. formData :: MonadIO m => BackEnd y -> HandlerT m ([(C.ByteString, C.ByteString)], [File y]) formData b = do   r <- request   liftIO $ parseRequestBody b r --- header :: Monad m => CI ByteString -> ByteString -> HandlerT m ()-+-- | Get all the request headers. headers :: Monad m => HandlerT m RequestHeaders headers = liftM requestHeaders request -jsonData :: (FromJSON a, MonadIO m) => HandlerT m (Either String a)+-- | Consume the request body as a JSON value. Returns a 'JsonInputError' on failure.+jsonData :: (FromJSON a, MonadIO m) => HandlerT m (Either JsonInputError a) jsonData = do   r <- request   ejs <- ST.evalStateT Pipes.Aeson.decode $ producerRequestBody r   return $! case ejs of-    Nothing -> Left "Request body exhausted while parsing JSON"+    Nothing -> Left RequestBodyExhausted     Just res -> case res of-      Left err -> Left $! case err of-        AttoparsecError err -> show err-        FromJSONError err -> err+      Left err -> Left $ JsonError err       Right r -> Right r --- param :: -+-- | Get all matched params. params :: Monad m => HandlerT m [Param] params = HandlerT (view L.params) -raw :: Monad m => L.ByteString -> HandlerT m ()-raw bs = HandlerT (bodySource .= LBSSource bs)--redirect :: Monad m => T.Text -> HandlerT m ()+-- | Terminate the current handler and send a @302 Found@ redirect to the provided URL.+-- Other headers that have already been set will also be returned in the request.+redirect :: Monad m => T.Text -- ^ URL to redirect to.+                    -> HandlerT m () redirect url = do   status found302   setHeader "Location" $ T.encodeUtf8 url   currentResponse >>= abort +-- | Get the underlying WAI 'Request' request :: Monad m => HandlerT m Request request = HandlerT $ view $ L.request -stream :: Monad m => StreamingBody -> HandlerT m ()-stream s = HandlerT (bodySource .= StreamSource s)-+-- | Return plain text as the response body. Sets the Content-Type header to \"text/plain; charset=utf-8\". text :: Monad m => TL.Text -> HandlerT m () text t = do   setHeader hContentType "text/plain; charset=utf-8"-  raw $ TL.encodeUtf8 t+  bytestring $ TL.encodeUtf8 t +-- | Return HTML as the response body. Sets the Content-Type header to \"text/html; charset=utf-8\".+-- If you're using something like blaze-html or lucid, you'll probably get better performance by rolling+-- your own function that sets the response body to a 'Builder'. html :: Monad m => TL.Text -> HandlerT m () html t = do   setHeader hContentType "text/html; charset=utf-8"-  raw $ TL.encodeUtf8 t+  bytestring $ TL.encodeUtf8 t +-- | Get the pattern that was matched in the router, e.g. @"/foo/:bar"@ routePattern :: Monad m => HandlerT m (Maybe T.Text) routePattern = HandlerT $ view $ L.matchedPattern++lookupParam :: (Functor m, Monad m, Parsable a) => C.ByteString -> HandlerT m (Maybe a)+lookupParam k = do+  mk <- lookup k <$> params+  case mk of+    Nothing -> return Nothing+    Just v -> do+      let ev = parseParam v+      case ev of+        Left err -> do+          status badRequest400+          text $ TL.fromStrict $ decodeUtf8 err+          currentResponse >>= abort+          return Nothing+        Right r -> return $ Just r++param :: (Functor m, Monad m, Parsable a) => C.ByteString -> HandlerT m a+param k = do+  p <- lookupParam k+  case p of+    Nothing -> do+      status badRequest400+      text $ "Missing required parameter " <> TL.fromStrict (decodeUtf8 k)+      currentResponse >>= abort+      param k+    Just r -> return r++raise :: Monad m => C.ByteString -> HandlerT m ()+raise msg = do+  status badRequest400+  text $ TL.fromStrict $ decodeUtf8 msg+  currentResponse >>= abort  runHandler :: Monad m => ResponseState -> Maybe T.Text -> Request -> [Param] -> HandlerT m a -> m (Either ResponseState (a, ResponseState)) runHandler rs pat rq ps m = runEitherT $ do
src/Web/Growler/Router.hs view
@@ -12,7 +12,6 @@     , patch     , addRoute     , matchAny-    , notFound     , capture     , regex     , function@@ -21,6 +20,8 @@     , route     , handlerHook     , RoutePattern(..)+    , notFound+    , internalServerError     ) where  import           Control.Arrow              ((***))@@ -62,23 +63,23 @@ handlerHook :: Monad m => (HandlerT m () -> HandlerT m ()) -> GrowlerT m () handlerHook f = GrowlerT $ modify' (fmap $ \(m, p, h) -> (m, p, f h)) --- | get = 'addroute' 'GET'+-- | get = 'addRoute' 'GET' get :: (MonadIO m) => RoutePattern -> HandlerT m () -> GrowlerT m () get = addRoute GET --- | post = 'addroute' 'POST'+-- | post = 'addRoute' 'POST' post :: (MonadIO m) => RoutePattern -> HandlerT m () -> GrowlerT m () post = addRoute POST --- | put = 'addroute' 'PUT'+-- | put = 'addRoute' 'PUT' put :: (MonadIO m) => RoutePattern -> HandlerT m () -> GrowlerT m () put = addRoute PUT --- | delete = 'addroute' 'DELETE'+-- | delete = 'addRoute' 'DELETE' delete :: (MonadIO m) => RoutePattern -> HandlerT m () -> GrowlerT m () delete = addRoute DELETE --- | patch = 'addroute' 'PATCH'+-- | patch = 'addRoute' 'PATCH' patch :: (MonadIO m) => RoutePattern -> HandlerT m () -> GrowlerT m () patch = addRoute PATCH @@ -86,11 +87,14 @@ matchAny :: (MonadIO m) => RoutePattern -> HandlerT m () -> GrowlerT m () matchAny pattern action = mapM_ (\v -> addRoute v pattern action) [minBound..maxBound] --- | Specify an action to take if nothing else is found. Note: this _always_ matches,--- so should generally be the last route specified.-notFound :: (MonadIO m) => HandlerT m ()+-- | A blank 404 Not Found handler for convenience.+notFound :: (Monad m) => HandlerT m () notFound = status status404 +-- | A blank 500 Internal Server Error handler for convenience.+internalServerError :: Monad m => HandlerT m ()+internalServerError = status status500+ -- | Define a route with a 'StdMethod', 'T.Text' value representing the path spec, -- and a body ('Action') which modifies the response. --@@ -114,7 +118,7 @@   else Nothing  matchRoute :: RoutePattern -> Request -> Maybe (T.Text, [Param])-matchRoute (RoutePattern p) req = let (pat, _, rps) = p req in case rps of+matchRoute (RoutePattern p) req = let (RoutePatternResult pat _ rps) = p req in case rps of   Fail -> Nothing   Partial _ -> Nothing   Complete ps -> Just (pat, ps)@@ -138,7 +142,7 @@ regex :: String -> RoutePattern regex pattern = RoutePattern go   where-    go req = (T.pack pattern, req, maybe Fail Complete $ fmap convertParams match)+    go req = RoutePatternResult (T.pack pattern) req $ maybe Fail Complete $ fmap convertParams match       where         rgx = Regex.mkRegex pattern         strip (_, match, _, subs) = match : subs@@ -173,13 +177,13 @@ -- HTTP/1.1 -- function :: (Request -> T.Text) -> (Request -> MatchResult) -> RoutePattern-function fn fps = RoutePattern $ \r -> (fn r, r, fps r)+function fn fps = RoutePattern $ \r -> RoutePatternResult (fn r) r (fps r)  -- | Build a route that requires the requested path match exactly, without captures. literal :: String -> RoutePattern literal pat = RoutePattern go   where-    go req = (packed, req { pathInfo = req' }, result)+    go req = RoutePatternResult packed (req { pathInfo = req' }) result       where         packed = T.pack pat         (result, req') = case T.stripPrefix packed (path req) of
src/Web/Growler/Types.hs view
@@ -10,6 +10,7 @@ module Web.Growler.Types where import           Blaze.ByteString.Builder  (Builder) import           Control.Applicative+import           Control.Exception import           Control.Lens.TH import           Control.Monad.Base (MonadBase(..), liftBaseDefault) import           Control.Monad.Reader@@ -31,12 +32,19 @@ import           Network.HTTP.Types.Method import           Network.HTTP.Types.Status import           Network.Wai+import           Pipes.Aeson                 (DecodingError (..))  data MatchResult = Fail | Partial [Param] | Complete [Param]   deriving (Show, Eq) -newtype RoutePattern = RoutePattern { runRoutePattern :: Request -> (Text, Request, MatchResult) }+newtype RoutePattern = RoutePattern { runRoutePattern :: Request -> RoutePatternResult } +data RoutePatternResult = RoutePatternResult+  { routePatternResultName        :: !Text+  , routePatternResultRequest     :: !Request     -- ^ The (potentially) updated request after consuming a portion of the path+  , routePatternResultMatchResult :: !MatchResult+  }+ instance Monoid MatchResult where   mappend l r = case l of     Fail -> Fail@@ -50,10 +58,10 @@   mempty = Partial []  instance Monoid RoutePattern where-  mappend (RoutePattern a) (RoutePattern b) = RoutePattern $ \r -> let (t1, r', p1) = a r in-                                                                   let (t2, r'', p2) = b r' in-                                                                     (t1 <> t2, r'', p1 <> p2)-  mempty = RoutePattern $ \r -> ("", r, Partial [])+  mappend (RoutePattern a) (RoutePattern b) = RoutePattern $ \r -> let (RoutePatternResult t1 r' p1) = a r in+                                                                   let (RoutePatternResult t2 r'' p2) = b r' in+                                                                     RoutePatternResult (t1 <> t2) r'' (p1 <> p2)+  mempty = RoutePattern $ \r -> RoutePatternResult "" r $ Partial []  instance IsString RoutePattern where   fromString = capture . T.pack@@ -69,7 +77,7 @@ capture :: Text -> RoutePattern capture pat = RoutePattern process   where -    process req = (pat, req { pathInfo = ss }, res)+    process req = RoutePatternResult pat (req { pathInfo = ss }) res       where         (res, ss) = go (T.split (== '/') pat) (T.split (== '/') $ path req) []         go [] [] prs = (Complete prs, []) -- request string and pattern match!@@ -84,7 +92,7 @@  type Param = (C.ByteString, C.ByteString) -data BodySource = FileSource !(FilePath, Maybe FilePart)+data BodySource = FileSource !FilePath !(Maybe FilePart)                 | BuilderSource !Builder                 | LBSSource !L.ByteString                 | StreamSource !StreamingBody@@ -129,7 +137,6 @@       return $ StHandlerT $ case res of         Left s -> Left s         Right (x, s, _) -> Right (x, s)-            restoreT mSt = HandlerT $ do     (StHandlerT stof) <- lift $ lift $ mSt@@ -163,4 +170,13 @@   liftIO = GrowlerT . liftIO  type Growler = GrowlerT IO++data JsonInputError = RequestBodyExhausted+                    | JsonError DecodingError+                    deriving (Show, Eq)++data GrowlerConfig m = GrowlerConfig+  { growlerConfigNotFoundHandler :: HandlerT m () -- ^ The 404 not found handler. If no route matches, then this handler will be evaluated.+  , growlerConfigErrorHandler    :: SomeException -> HandlerT m () -- ^ The uncaught exception handler. If an exception is thrown and not caught while trying to service a request, then this handler will be evaluated.+  }