packages feed

scotty 0.11.5 → 0.11.6

raw patch · 20 files changed

+358/−73 lines, 20 filesdep +base-compat-batteriesdep +luciddep +weighdep ~basedep ~bytestringdep ~data-default-classnew-uploader

Dependencies added: base-compat-batteries, lucid, weigh

Dependency ranges changed: base, bytestring, data-default-class, http-types, mtl, text, transformers

Files

README.md view
@@ -37,6 +37,6 @@  ### Development & Support -Open an issue on GitHub or join `#scotty` on Freenode.+Open an issue on GitHub.  Copyright (c) 2012-2019 Andrew Farmer
Web/Scotty.hs view
@@ -26,7 +26,7 @@       -- definition, as they completely replace the current 'Response' body.     , text, html, file, json, stream, raw       -- ** Exceptions-    , raise, rescue, next, finish, defaultHandler, liftAndCatchIO+    , raise, raiseStatus, rescue, next, finish, defaultHandler, liftAndCatchIO       -- * Parsing Parameters     , Param, Trans.Parsable(..), Trans.readEither       -- * Types@@ -93,6 +93,10 @@ -- turn into HTTP 500 responses. raise :: Text -> ActionM a raise = Trans.raise++-- | Throw an exception, which can be caught with 'rescue'. Uncaught exceptions turn into HTTP responses corresponding to the given status.+raiseStatus :: Status -> Text -> ActionM a+raiseStatus = Trans.raiseStatus  -- | Abort execution of this action and continue pattern matching routes. -- Like an exception, any code after 'next' is not executed.
Web/Scotty/Action.hs view
@@ -18,6 +18,7 @@     , param     , params     , raise+    , raiseStatus     , raw     , readEither     , redirect@@ -37,8 +38,8 @@  import qualified Control.Exception          as E import           Control.Monad.Error.Class-import           Control.Monad.Reader-import qualified Control.Monad.State        as MS+import           Control.Monad.Reader       hiding (mapM)+import qualified Control.Monad.State.Strict as MS import           Control.Monad.Trans.Except  import qualified Data.Aeson                 as A@@ -47,19 +48,24 @@ import qualified Data.CaseInsensitive       as CI import           Data.Default.Class         (def) import           Data.Int-#if !(MIN_VERSION_base(4,8,0))-import           Data.Monoid                (mconcat)-#endif import qualified Data.Text                  as ST+import qualified Data.Text.Encoding         as STE import qualified Data.Text.Lazy             as T import           Data.Text.Lazy.Encoding    (encodeUtf8) import           Data.Word  import           Network.HTTP.Types+-- not re-exported until version 0.11+#if !MIN_VERSION_http_types(0,11,0)+import           Network.HTTP.Types.Status+#endif import           Network.Wai  import           Numeric.Natural +import           Prelude ()+import           Prelude.Compat+ import           Web.Scotty.Internal.Types import           Web.Scotty.Util @@ -79,18 +85,24 @@ defH _          (Redirect url)    = do     status status302     setHeader "Location" url-defH Nothing    (ActionError e)   = do-    status status500-    html $ mconcat ["<h1>500 Internal Server Error</h1>", showError e]-defH h@(Just f) (ActionError e)   = f e `catchError` (defH h) -- so handlers can throw exceptions themselves+defH Nothing    (ActionError s e)   = do+    status s+    let code = T.pack $ show $ statusCode s+    let msg = T.fromStrict $ STE.decodeUtf8 $ statusMessage s+    html $ mconcat ["<h1>", code, " ", msg, "</h1>", showError e]+defH h@(Just f) (ActionError _ e)   = f e `catchError` (defH h) -- so handlers can throw exceptions themselves defH _          Next              = next defH _          Finish            = return ()  -- | Throw an exception, which can be caught with 'rescue'. Uncaught exceptions -- turn into HTTP 500 responses. raise :: (ScottyError e, Monad m) => e -> ActionT e m a-raise = throwError . ActionError+raise = raiseStatus status500 +-- | Throw an exception, which can be caught with 'rescue'. Uncaught exceptions turn into HTTP responses corresponding to the given status.+raiseStatus :: (ScottyError e, Monad m) => Status -> e -> ActionT e m a+raiseStatus s = throwError . ActionError s+ -- | Abort execution of this action and continue pattern matching routes. -- Like an exception, any code after 'next' is not executed. --@@ -113,8 +125,8 @@ -- > raise "just kidding" `rescue` (\msg -> text msg) rescue :: (ScottyError e, Monad m) => ActionT e m a -> (e -> ActionT e m a) -> ActionT e m a rescue action h = catchError action $ \e -> case e of-    ActionError err -> h err            -- handle errors-    other           -> throwError other -- rethrow internal error types+    ActionError _ err -> h err            -- handle errors+    other             -> throwError other -- rethrow internal error types  -- | Like 'liftIO', but catch any IO exceptions and turn them into 'ScottyError's. liftAndCatchIO :: (ScottyError e, MonadIO m) => IO a -> ActionT e m a@@ -172,11 +184,35 @@ bodyReader :: Monad m => ActionT e m (IO B.ByteString) bodyReader = ActionT $ getBodyChunk `liftM` ask --- | Parse the request body as a JSON object and return it. Raises an exception if parse is unsuccessful.+-- | Parse the request body as a JSON object and return it.+--+--   If the JSON object is malformed, this sets the status to+--   400 Bad Request, and throws an exception.+--+--   If the JSON fails to parse, this sets the status to+--   422 Unprocessable Entity.+--+--   These status codes are as per https://www.restapitutorial.com/httpstatuscodes.html. jsonData :: (A.FromJSON a, ScottyError e, MonadIO m) => ActionT e m a jsonData = do     b <- body-    either (\e -> raise $ stringError $ "jsonData - no parse: " ++ e ++ ". Data was:" ++ BL.unpack b) return $ A.eitherDecode b+    when (b == "") $ do+      let htmlError = "jsonData - No data was provided."+      raiseStatus status400 $ stringError htmlError+    case A.eitherDecode b of+      Left err -> do+        let htmlError = "jsonData - malformed."+              `mappend` " Data was: " `mappend` BL.unpack b+              `mappend` " Error was: " `mappend` err+        raiseStatus status400 $ stringError htmlError+      Right value -> case A.fromJSON value of+        A.Error err -> do+          let htmlError = "jsonData - failed parse."+                `mappend` " Data was: " `mappend` BL.unpack b `mappend` "."+                `mappend` " Error was: " `mappend` err+          raiseStatus status422 $ stringError htmlError+        A.Success a -> do+          return a  -- | Get a parameter. First looks in captures, then form data, then query parameters. --
Web/Scotty/Internal/Types.hs view
@@ -16,16 +16,13 @@ import           Control.Monad.Error.Class import qualified Control.Monad.Fail as Fail import           Control.Monad.Reader-import           Control.Monad.State+import           Control.Monad.State.Strict import           Control.Monad.Trans.Control (MonadBaseControl, StM, liftBaseWith, restoreM, ComposeSt, defaultLiftBaseWith, defaultRestoreM, MonadTransControl, StT, liftWith, restoreT) import           Control.Monad.Trans.Except  import qualified Data.ByteString as BS import           Data.ByteString.Lazy.Char8 (ByteString) import           Data.Default.Class (Default, def)-#if !(MIN_VERSION_base(4,8,0))-import           Data.Monoid (mempty)-#endif import           Data.String (IsString(..)) import           Data.Text.Lazy (Text, pack) import           Data.Typeable (Typeable)@@ -37,6 +34,9 @@ import           Network.Wai.Handler.Warp (Settings, defaultSettings) import           Network.Wai.Parse (FileInfo) +import           Prelude ()+import           Prelude.Compat+ --------------------- Options ----------------------- data Options = Options { verbose :: Int -- ^ 0 = silent, 1(def) = startup banner                        , settings :: Settings -- ^ Warp 'Settings'@@ -79,10 +79,11 @@   ------------------ Scotty Errors ---------------------data ActionError e = Redirect Text-                   | Next-                   | Finish-                   | ActionError e+data ActionError e+  = Redirect Text+  | Next+  | Finish+  | ActionError Status e  -- | In order to use a custom exception type (aside from 'Text'), you must -- define an instance of 'ScottyError' for that type.@@ -95,11 +96,11 @@     showError = id  instance ScottyError e => ScottyError (ActionError e) where-    stringError = ActionError . stringError+    stringError = ActionError status500 . stringError     showError (Redirect url)  = url     showError Next            = pack "Next"     showError Finish          = pack "Finish"-    showError (ActionError e) = showError e+    showError (ActionError _ e) = showError e  type ErrorHandler e m = Maybe (e -> ActionT e m ()) @@ -196,6 +197,58 @@     type StM (ActionT e m) a = ComposeSt (ActionT e) m a     liftBaseWith = defaultLiftBaseWith     restoreM     = defaultRestoreM++instance (MonadReader r m, ScottyError e) => MonadReader r (ActionT e m) where+    {-# INLINE ask #-}+    ask = lift ask+    {-# INLINE local #-}+    local f = ActionT . mapExceptT (mapReaderT (mapStateT $ local f)) . runAM++instance (MonadState s m, ScottyError e) => MonadState s (ActionT e m) where+    {-# INLINE get #-}+    get = lift get+    {-# INLINE put #-}+    put = lift . put++instance (Semigroup a) => Semigroup (ScottyT e m a) where+  x <> y = (<>) <$> x <*> y++instance+  ( Monoid a+#if !(MIN_VERSION_base(4,11,0))+  , Semigroup a+#endif+#if !(MIN_VERSION_base(4,8,0))+  , Functor m+#endif+  ) => Monoid (ScottyT e m a) where+  mempty = return mempty+#if !(MIN_VERSION_base(4,11,0))+  mappend = (<>)+#endif++instance+  ( Monad m+#if !(MIN_VERSION_base(4,8,0))+  , Functor m+#endif+  , Semigroup a+  ) => Semigroup (ActionT e m a) where+  x <> y = (<>) <$> x <*> y++instance+  ( Monad m, ScottyError e, Monoid a+#if !(MIN_VERSION_base(4,11,0))+  , Semigroup a+#endif+#if !(MIN_VERSION_base(4,8,0))+  , Functor m+#endif+  ) => Monoid (ActionT e m a) where+  mempty = return mempty+#if !(MIN_VERSION_base(4,11,0))+  mappend = (<>)+#endif  ------------------ Scotty Routes -------------------- data RoutePattern = Capture   Text
Web/Scotty/Route.hs view
@@ -14,9 +14,6 @@ import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as BL import           Data.Maybe (fromMaybe, isJust)-#if !(MIN_VERSION_base(4,8,0))-import           Data.Monoid (mconcat)-#endif import           Data.String (fromString) import qualified Data.Text.Lazy as T import qualified Data.Text as TS@@ -28,6 +25,9 @@ #endif import qualified Network.Wai.Parse as Parse hiding (parseRequestBody) +import           Prelude ()+import           Prelude.Compat+ import qualified Text.Regex as Regex  import           Web.Scotty.Action@@ -109,7 +109,7 @@ matchRoute (Literal pat)  req | pat == path req = Just []                               | otherwise       = Nothing matchRoute (Function fun) req = fun req-matchRoute (Capture pat)  req = go (T.split (=='/') pat) (T.split (=='/') $ path req) []+matchRoute (Capture pat)  req = go (T.split (=='/') pat) (compress $ T.split (=='/') $ path req) []     where go [] [] prs = Just prs -- request string and pattern match!           go [] r  prs | T.null (mconcat r)  = Just prs -- in case request has trailing slashes                        | otherwise           = Nothing  -- request string is longer than pattern@@ -119,6 +119,9 @@                                | T.null p        = Nothing      -- p is null, but r is not, fail                                | T.head p == ':' = go ps rs $ (T.tail p, r) : prs -- p is a capture, add to params                                | otherwise       = Nothing      -- both literals, but unequal, fail+          compress ("":rest@("":_)) = compress rest+          compress (x:xs) = x : compress xs+          compress [] = []  -- Pretend we are at the top level. path :: Request -> T.Text
Web/Scotty/Trans.hs view
@@ -30,7 +30,7 @@       -- definition, as they completely replace the current 'Response' body.     , text, html, file, json, stream, raw       -- ** Exceptions-    , raise, rescue, next, finish, defaultHandler, ScottyError(..), liftAndCatchIO+    , raise, raiseStatus, rescue, next, finish, defaultHandler, ScottyError(..), liftAndCatchIO       -- * Parsing Parameters     , Param, Parsable(..), readEither       -- * Types@@ -42,7 +42,7 @@ import Blaze.ByteString.Builder (fromByteString)  import Control.Monad (when)-import Control.Monad.State (execState, modify)+import Control.Monad.State.Strict (execState, modify) import Control.Monad.IO.Class  import Data.Default.Class (def)
Web/Scotty/Util.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} module Web.Scotty.Util     ( lazyTextToStrictByteString     , strictByteStringToLazyText
+ bench/Main.hs view
@@ -0,0 +1,54 @@+{-# language+    OverloadedStrings+  , GeneralizedNewtypeDeriving+  #-}++module Main (main) where++import Control.Monad+import Data.Default.Class (def)+import Data.Functor.Identity+import Data.Text (Text)+import Lucid.Base+import Lucid.Html5+import Web.Scotty+import Web.Scotty.Internal.Types+import qualified Control.Monad.State.Lazy as SL+import qualified Control.Monad.State.Strict as SS+import qualified Data.ByteString.Lazy as BL++import Weigh++main :: IO ()+main = do+  mainWith $ do+    setColumns [Case,Allocated,GCs,Live,Check,Max,MaxOS]+    setFormat Markdown+    io "ScottyM Strict" BL.putStr+      (SS.evalState (runS $ renderBST htmlScotty) def)+    io "ScottyM Lazy" BL.putStr+      (SL.evalState (runScottyLazy $ renderBST htmlScottyLazy) def)+    io "Identity" BL.putStr+      (runIdentity $ renderBST htmlIdentity)++htmlTest :: Monad m => HtmlT m ()+htmlTest = replicateM_ 2 $ div_ $ do+  replicateM_ 1000 $ div_ $ do+    replicateM_ 10000 $ div_ "test"++htmlIdentity :: HtmlT Identity ()+htmlIdentity = htmlTest+{-# noinline htmlIdentity #-}++htmlScotty :: HtmlT ScottyM ()+htmlScotty = htmlTest+{-# noinline htmlScotty #-}++htmlScottyLazy :: HtmlT ScottyLazy ()+htmlScottyLazy = htmlTest+{-# noinline htmlScottyLazy #-}++newtype ScottyLazy a = ScottyLazy+  { runScottyLazy:: SL.State (ScottyState Text IO) a }+  deriving (Functor,Applicative,Monad)+
changelog.md view
@@ -1,3 +1,17 @@+## 0.12 [2020.05.16]+* Provide `MonadReader` and `MonadState` instances for `ActionT`.+* Add HTTP Status code as a field to `ActionError`, and add+  a sister function to `raise`, `raiseStatus`. This makes+  throwing a specific error code and exiting much cleaner, and+  avoids the strange defaulting to HTTP 500. This will make internal+  functions easier to implement with the right status codes 'thrown',+  such as `jsonData`.+* Correct http statuses returned by `jsonData` (#228).+* Better error message when no data is provided to `jsonData` (#226).+* Add `Semigroup` and `Monoid` instances for `ActionT` and `ScottyT`+* ScottyT: Use strict StateT instead of lazy+* Handle adjacent slashes in the request path as one (thanks @SkyWriter)+ ## 0.11.5 [2019.09.07] * Allow building the test suite with `hspec-wai-0.10`. 
+ examples/LICENSE view
@@ -0,0 +1,29 @@+Copyright (c) 2012-2017 Andrew Farmer+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Andrew Farmer nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
examples/basic.hs view
@@ -7,14 +7,14 @@  import Control.Monad import Control.Monad.Trans-import Data.Monoid import System.Random (newStdGen, randomRs)  import Network.HTTP.Types (status302)  import Data.Text.Lazy.Encoding (decodeUtf8) import Data.String (fromString)-import Prelude+import Prelude ()+import Prelude.Compat  main :: IO () main = scotty 3000 $ do
+ examples/bodyecho.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings #-}+module Main (main) where++import Web.Scotty++import Control.Monad.IO.Class (liftIO)+import qualified Blaze.ByteString.Builder as B+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import qualified Data.Text.Lazy as T++main :: IO ()+main = scotty 3000 $ do+    post "/echo" $ do+        rd <- bodyReader+        stream $ ioCopy rd $ return ()++    post "/count" $ do+        wb <- body -- this must happen before first 'rd'+        rd <- bodyReader+        let step acc = do +              chunk <- rd+              putStrLn "got a chunk"+              let len = BS.length chunk+              if len > 0 +                then step $ acc + len+                else return acc+        len <- liftIO $ step 0+        text $ T.pack $ "uploaded " ++ show len ++ " bytes, wb len is " ++ show (BSL.length wb)+++ioCopy :: IO BS.ByteString -> IO () -> (B.Builder -> IO ()) -> IO () -> IO ()+ioCopy reader close write flush = step >> flush where+   step = do chunk <- reader+             if (BS.length chunk > 0) +               then (write $ B.insertByteString chunk) >> step+               else close
examples/exceptions.hs view
@@ -3,13 +3,13 @@  import Control.Monad.IO.Class -import Data.Monoid import Data.String (fromString)  import Network.HTTP.Types import Network.Wai.Middleware.RequestLogger -import Prelude+import Prelude ()+import Prelude.Compat  import System.Random 
examples/globalstate.hs view
@@ -9,7 +9,6 @@ -- embedded into any MonadIO monad. module Main (main) where -import Control.Applicative import Control.Concurrent.STM import Control.Monad.Reader @@ -19,7 +18,8 @@  import Network.Wai.Middleware.RequestLogger -import Prelude+import Prelude ()+import Prelude.Compat  import Web.Scotty.Trans 
+ examples/reader.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}++{-+    An example of embedding a custom monad into Scotty's transformer+    stack, using ReaderT to provide access to a global state.+-}+module Main where++import Control.Monad.Reader (MonadIO, MonadReader, ReaderT, asks, lift, runReaderT)+import Data.Default.Class (def)+import Data.Text.Lazy (Text, pack)+import Prelude ()+import Prelude.Compat+import Web.Scotty.Trans (ScottyT, get, scottyOptsT, text)++data Config = Config+  { environment :: String+  } deriving (Eq, Read, Show)++newtype ConfigM a = ConfigM+  { runConfigM :: ReaderT Config IO a+  } deriving (Applicative, Functor, Monad, MonadIO, MonadReader Config)++application :: ScottyT Text ConfigM ()+application = do+  get "/" $ do+    e <- lift $ asks environment+    text $ pack $ show e++main :: IO ()+main = scottyOptsT def runIO application where+  runIO :: ConfigM a -> IO a+  runIO m = runReaderT (runConfigM m) config++  config :: Config+  config = Config+    { environment = "Development"+    }
examples/upload.hs view
@@ -4,7 +4,6 @@ import Web.Scotty  import Control.Monad.IO.Class-import Data.Monoid  import Network.Wai.Middleware.RequestLogger import Network.Wai.Middleware.Static@@ -17,7 +16,8 @@ import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Char8 as BS import System.FilePath ((</>))-import Prelude+import Prelude ()+import Prelude.Compat  main :: IO () main = scotty 3000 $ do
+ examples/uploads/.keep view
examples/urlshortener.hs view
@@ -6,13 +6,13 @@ import Control.Concurrent.MVar import Control.Monad.IO.Class import qualified Data.Map as M-import Data.Monoid import qualified Data.Text.Lazy as T  import Network.Wai.Middleware.RequestLogger import Network.Wai.Middleware.Static -import Prelude+import Prelude ()+import Prelude.Compat  import qualified Text.Blaze.Html5 as H import Text.Blaze.Html5.Attributes
scotty.cabal view
@@ -1,5 +1,5 @@ Name:                scotty-Version:             0.11.5+Version:             0.11.6 Synopsis:            Haskell web framework inspired by Ruby's Sinatra, using WAI and Warp Homepage:            https://github.com/scotty-web/scotty Bug-reports:         https://github.com/scotty-web/scotty/issues@@ -51,21 +51,17 @@                    , GHC == 8.2.2                    , GHC == 8.4.4                    , GHC == 8.6.5-                   , GHC == 8.8.1+                   , GHC == 8.8.3+                   , GHC == 8.10.1 Extra-source-files:     README.md     changelog.md     examples/404.html-    examples/basic.hs-    examples/cookies.hs-    examples/exceptions.hs-    examples/globalstate.hs-    examples/gzip.hs-    examples/options.hs-    examples/upload.hs-    examples/urlshortener.hs+    examples/LICENSE+    examples/*.hs     examples/static/jquery.js     examples/static/jquery-json.js+    examples/uploads/.keep  Library   Exposed-modules:     Web.Scotty@@ -75,27 +71,28 @@                        Web.Scotty.Route                        Web.Scotty.Util   default-language:    Haskell2010-  build-depends:       aeson               >= 0.6.2.1  && < 1.5,-                       base                >= 4.6      && < 5,-                       blaze-builder       >= 0.3.3.0  && < 0.5,-                       bytestring          >= 0.10.0.2 && < 0.11,-                       case-insensitive    >= 1.0.0.1  && < 1.3,-                       data-default-class  >= 0.0.1    && < 0.2,-                       exceptions          >= 0.7      && < 0.11,+  build-depends:       aeson                 >= 0.6.2.1  && < 1.5,+                       base                  >= 4.6      && < 5,+                       base-compat-batteries >= 0.10     && < 0.12,+                       blaze-builder         >= 0.3.3.0  && < 0.5,+                       bytestring            >= 0.10.0.2 && < 0.11,+                       case-insensitive      >= 1.0.0.1  && < 1.3,+                       data-default-class    >= 0.0.1    && < 0.2,+                       exceptions            >= 0.7      && < 0.11,                        fail,-                       http-types          >= 0.8.2    && < 0.13,-                       monad-control       >= 1.0.0.3  && < 1.1,-                       mtl                 >= 2.1.2    && < 2.3,-                       nats                >= 0.1      && < 2,-                       network             >= 2.6.0.2  && < 3.2,-                       regex-compat        >= 0.95.1   && < 0.96,-                       text                >= 0.11.3.1 && < 1.3,-                       transformers        >= 0.3.0.0  && < 0.6,-                       transformers-base   >= 0.4.1    && < 0.5,-                       transformers-compat >= 0.4      && < 0.7,-                       wai                 >= 3.0.0    && < 3.3,-                       wai-extra           >= 3.0.0    && < 3.1,-                       warp                >= 3.0.13   && < 3.4+                       http-types            >= 0.9.1    && < 0.13,+                       monad-control         >= 1.0.0.3  && < 1.1,+                       mtl                   >= 2.1.2    && < 2.3,+                       nats                  >= 0.1      && < 2,+                       network               >= 2.6.0.2  && < 3.2,+                       regex-compat          >= 0.95.1   && < 0.96,+                       text                  >= 0.11.3.1 && < 1.3,+                       transformers          >= 0.3.0.0  && < 0.6,+                       transformers-base     >= 0.4.1    && < 0.5,+                       transformers-compat   >= 0.4      && < 0.7,+                       wai                   >= 3.0.0    && < 3.3,+                       wai-extra             >= 3.0.0    && < 3.1,+                       warp                  >= 3.0.13   && < 3.4    GHC-options: -Wall -fno-warn-orphans @@ -120,6 +117,22 @@                        wai   build-tool-depends:  hspec-discover:hspec-discover == 2.*   GHC-options:         -Wall -threaded -fno-warn-orphans++benchmark weigh+  main-is:             Main.hs+  type:                exitcode-stdio-1.0+  default-language:    Haskell2010+  hs-source-dirs:      bench+  build-depends:       base,+                       scotty,+                       lucid,+                       bytestring,+                       mtl,+                       text,+                       transformers,+                       data-default-class,+                       weigh == 0.0.16+  GHC-options:         -Wall -O2 -threaded  source-repository head   type:     git
test/Web/ScottySpec.hs view
@@ -48,6 +48,10 @@           it ("adds route for " ++ method ++ " requests") $ do             makeRequest "/scotty" `shouldRespondWith` 200 +        withApp (route "/scotty" $ html "") $ do+          it ("properly handles extra slash routes for " ++ method ++ " requests") $ do+            makeRequest "//scotty" `shouldRespondWith` 200+     describe "addroute" $ do       forM_ availableMethods $ \method -> do         withApp (addroute method "/scotty" $ html "") $ do