Skip to content

Commit

Permalink
query and form parameters don't call next, add tests, WIP capturePara…
Browse files Browse the repository at this point in the history
…m 500 test case
  • Loading branch information
Marco Zocca committed Sep 25, 2023
1 parent 289ce9f commit 483a84d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 10 deletions.
35 changes: 26 additions & 9 deletions Web/Scotty/Action.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}
module Web.Scotty.Action
( addHeader
, body
Expand Down Expand Up @@ -236,7 +237,7 @@ jsonData = do
-- This means captures are somewhat typed, in that a route won't match if a correctly typed
-- capture cannot be parsed.
param :: (Parsable a, ScottyError e, Monad m) => T.Text -> ActionT e m a
param = paramWith "" getParams status500
param = paramWith Nothing getParams status500
{-# DEPRECATED param "(#204) Not a good idea to treat all parameters identically. Use captureParam, formParam and queryParam instead. "#-}

-- | Get a capture parameter.
Expand All @@ -245,35 +246,51 @@ param = paramWith "" getParams status500
--
-- * If parameter is found, but 'parseParam' fails to parse to the correct type, 'next' is called.
captureParam :: (Parsable a, ScottyError e, Monad m) => T.Text -> ActionT e m a
captureParam = paramWith "Capture" getCaptureParams status500
captureParam = paramWith (Just CaptureParam) getCaptureParams status500

-- | Get a form parameter.
--
-- * Raises an exception which can be caught by 'rescue' if parameter is not found. If the exception is not caught, scotty will return a HTTP error code 400 ("Bad Request") to the client.
--
-- * If parameter is found, but 'parseParam' fails to parse to the correct type, 'next' is called.
formParam :: (Parsable a, ScottyError e, Monad m) => T.Text -> ActionT e m a
formParam = paramWith "Form" getFormParams status400
formParam = paramWith (Just FormParam) getFormParams status400

-- | Get a query parameter.
--
-- * Raises an exception which can be caught by 'rescue' if parameter is not found. If the exception is not caught, scotty will return a HTTP error code 400 ("Bad Request") to the client.
--
-- * If parameter is found, but 'parseParam' fails to parse to the correct type, 'next' is called.
queryParam :: (Parsable a, ScottyError e, Monad m) => T.Text -> ActionT e m a
queryParam = paramWith "Query" getQueryParams status400
queryParam = paramWith (Just QueryParam) getQueryParams status400

data ParamType = CaptureParam
| FormParam
| QueryParam
instance Show ParamType where
show = \case
CaptureParam -> "capture"
FormParam -> "form"
QueryParam -> "query"

paramWith :: (ScottyError e, Monad m, Parsable b) =>
String -- ^ String representation of the type of query parameter
Maybe ParamType
-> (ActionEnv -> [Param])
-> Status -- ^ HTTP status to return if parameter is not found
-> T.Text -- ^ parameter name
-> ActionT e m b
paramWith tys f err k = do
paramWith tym f err k = do
val <- ActionT $ liftM (lookup k . f) ask
case val of
Nothing -> raiseStatus err $ stringError (tys <> " parameter: " ++ T.unpack k ++ " not found!")
Just v -> either (const next) return $ parseParam v
case tym of
Nothing -> raiseStatus err $ stringError (unwords ["Parameter:", T.unpack k, "not found!"])
Just ty ->
case val of
Nothing -> raiseStatus err $ stringError (unwords [show ty, "parameter:", T.unpack k, "not found!"])
Just v ->
let handleParseError = \case
CaptureParam -> next
_ -> raiseStatus err $ stringError (unwords ["Cannot parse", T.unpack v, "as a", show ty, "parameter"])
in either (const $ handleParseError ty) return $ parseParam v

-- | Get all parameters from capture, form and query (in that order).
params :: Monad m => ActionT e m [Param]
Expand Down
1 change: 0 additions & 1 deletion Web/Scotty/Route.hs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ mkEnv req captures opts = do
let
convert (k, v) = (strictByteStringToLazyText k, strictByteStringToLazyText v)
formparams' = map convert formparams
-- parameters = captures ++ map convert formparams ++ queryparams
queryparams = parseEncodedParams $ rawQueryString req

return $ Env req captures formparams' queryparams bs safeBodyReader [ (strictByteStringToLazyText k, fi) | (k,fi) <- fs ]
Expand Down
38 changes: 38 additions & 0 deletions test/Web/ScottySpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,55 @@ spec = do
get "/dictionary?word2=scotty" `shouldRespondWith` "scotty"
get "/dictionary?word1=a&word2=b" `shouldRespondWith` "a"

describe "captureParam" $ do
withApp (
do
Scotty.matchAny "/search/:q" $ do
v <- captureParam "q"
let y = v :: Int

Check warning on line 116 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

Defined but not used: ‘y’

Check warning on line 116 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

Defined but not used: ‘y’

Check warning on line 116 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

Defined but not used: ‘y’

Check warning on line 116 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

Defined but not used: ‘y’

Check warning on line 116 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.2

Defined but not used: ‘y’
text "int"
Scotty.matchAny "/search/:q" $ do
v <- captureParam "q"
let y = v :: String

Check warning on line 120 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

Defined but not used: ‘y’

Check warning on line 120 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

Defined but not used: ‘y’

Check warning on line 120 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

Defined but not used: ‘y’

Check warning on line 120 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

Defined but not used: ‘y’

Check warning on line 120 in test/Web/ScottySpec.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.2

Defined but not used: ‘y’
text "string"
) $ do
it "responds with 200 OK iff at least one route match at the right type" $ do
get "/search/42" `shouldRespondWith` 200 { matchBody = "int" }
get "/search/potato" `shouldRespondWith` 200 { matchBody = "string" }
withApp (
do
Scotty.matchAny "/search/:q" $ do
v <- captureParam "q"
json (v :: Int)
) $ do
it "responds with 500 Server Error if no route matches at the right type" $ do
get "/search/potato" `shouldRespondWith` 500

describe "queryParam" $ do
withApp (Scotty.matchAny "/search" $ queryParam "query" >>= text) $ do
it "returns query parameter with given name" $ do
get "/search?query=haskell" `shouldRespondWith` "haskell"
withApp (Scotty.matchAny "/search" (do
v <- queryParam "query"
json (v :: Int) )) $ do
it "responds with 200 OK if the query parameter can be parsed at the right type" $ do
get "/search?query=42" `shouldRespondWith` 200
it "responds with 400 Bad Request if the query parameter cannot be parsed at the right type" $ do
get "/search?query=potato" `shouldRespondWith` 400

describe "formParam" $ do
withApp (Scotty.matchAny "/search" $ formParam "query" >>= text) $ do
it "returns form parameter with given name" $ do
request "POST" "/search" [("Content-Type","application/x-www-form-urlencoded")] "query=haskell" `shouldRespondWith` "haskell"
it "replaces non UTF-8 bytes with Unicode replacement character" $ do
request "POST" "/search" [("Content-Type","application/x-www-form-urlencoded")] "query=\xe9" `shouldRespondWith` "\xfffd"
withApp (Scotty.matchAny "/search" (do
v <- formParam "query"
json (v :: Int))) $ do
it "responds with 200 OK if the form parameter can be parsed at the right type" $ do
request "POST" "/search" [("Content-Type","application/x-www-form-urlencoded")] "query=42" `shouldRespondWith` 200
it "responds with 400 Bad Request if the form parameter cannot be parsed at the right type" $ do
request "POST" "/search" [("Content-Type","application/x-www-form-urlencoded")] "query=potato" `shouldRespondWith` 400


describe "requestLimit" $ do
Expand Down

0 comments on commit 483a84d

Please sign in to comment.