Skip to content

Commit 9bb4477

Browse files
committed
Add replacePlus to parseUrlencoded and drop FromForm and ToForm classes
1 parent cbdb7ca commit 9bb4477

File tree

8 files changed

+57
-58
lines changed

8 files changed

+57
-58
lines changed

src/Hyper/Form.purs

+8-34
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@ module Hyper.Form
33
, optional
44
, required
55
, parseForm
6-
, fromForm
7-
, toForm
8-
, class FromForm
9-
, class ToForm
10-
, parseFromForm
116
) where
127

138
import Prelude
14-
import Data.Tuple as Tuple
15-
import Control.IxMonad (ibind, ipure, (:>>=))
9+
10+
import Control.IxMonad (ibind, ipure)
1611
import Control.Monad.Error.Class (throwError)
1712
import Data.Array (head)
1813
import Data.Either (Either(..))
@@ -25,7 +20,9 @@ import Data.Newtype (class Newtype, unwrap)
2520
import Data.StrMap (lookup)
2621
import Data.String (Pattern(Pattern), split)
2722
import Data.Tuple (Tuple)
23+
import Data.Tuple as Tuple
2824
import Hyper.Conn (Conn)
25+
import Hyper.Form.Urlencoded (Options) as Urlencoded
2926
import Hyper.Form.Urlencoded (parseUrlencoded)
3027
import Hyper.Middleware (Middleware)
3128
import Hyper.Middleware.Class (getConn)
@@ -64,45 +61,22 @@ parseForm ∷ forall m req res c
6461
. Monad m
6562
=> Request req m
6663
=> ReadableBody req m String
67-
=> Middleware
64+
=> Urlencoded.Options
65+
-> Middleware
6866
m
6967
(Conn req res c)
7068
(Conn req res c)
7169
(Either String Form)
72-
parseForm = do
70+
parseForm opts = do
7371
conn <- getConn
7472
{ headers } <- getRequestData
7573
body <- readBody
7674
case lookup "content-type" headers >>= parseContentMediaType of
7775
Nothing ->
7876
ipure (Left "Missing or invalid content-type header.")
7977
Just mediaType | mediaType == applicationFormURLEncoded ->
80-
ipure (Form <$> parseUrlencoded body)
78+
ipure (Form <$> parseUrlencoded opts body)
8179
Just mediaType ->
8280
ipure (Left ("Cannot parse media of type: " <> show mediaType))
8381
where bind = ibind
8482

85-
86-
class ToForm a where
87-
toForm a Form
88-
89-
90-
class FromForm a where
91-
fromForm Form Either String a
92-
93-
94-
parseFromForm forall m req res c a
95-
. Monad m
96-
=> Request req m
97-
=> ReadableBody req m String
98-
=> FromForm a
99-
=> Middleware
100-
m
101-
(Conn req res c)
102-
(Conn req res c)
103-
(Either String a)
104-
parseFromForm =
105-
parseForm :>>=
106-
case _ of
107-
Left err -> ipure (Left err)
108-
Right form -> ipure (fromForm form)

src/Hyper/Form/Urlencoded.purs

+23-8
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
11
-- | Parser for the `application/x-www-form-urlencoded` format, commonly used
22
-- | for query strings and POST request bodies.
33
module Hyper.Form.Urlencoded
4-
( parseUrlencoded
4+
( defaultOptions
5+
, Options
6+
, parseUrlencoded
57
) where
68

79
import Prelude
10+
811
import Control.Monad.Error.Class (throwError)
912
import Data.Array as Array
1013
import Data.Either (Either)
1114
import Data.Maybe (Maybe(Just, Nothing))
12-
import Data.String (split, joinWith, Pattern(Pattern))
15+
import Data.String (Pattern(..), Replacement(..), joinWith, replaceAll, split)
1316
import Data.Traversable (sequence)
1417
import Data.Tuple (Tuple(Tuple))
1518
import Global (decodeURIComponent)
1619

17-
toTuple :: Array String -> Either String (Tuple String (Maybe String))
18-
toTuple kv =
20+
toTuple :: Options -> Array String -> Either String (Tuple String (Maybe String))
21+
toTuple opts kv =
1922
case kv of
2023
[key] ->
2124
pure (Tuple (decodeURIComponent key) Nothing)
2225
[key, value] ->
23-
pure (Tuple (decodeURIComponent key) (Just (decodeURIComponent value)))
26+
let
27+
value' =
28+
if opts.replacePlus
29+
then
30+
replaceAll (Pattern "+") (Replacement " ") value
31+
else
32+
value
33+
in
34+
pure (Tuple (decodeURIComponent key) (Just (decodeURIComponent value')))
2435
parts ->
2536
throwError ("Invalid form key-value pair: " <> joinWith " " parts)
2637

38+
type Options = { replacePlus :: Boolean }
39+
40+
defaultOptions :: Options
41+
defaultOptions = { replacePlus: true }
2742

28-
parseUrlencoded :: String Either String (Array (Tuple String (Maybe String)))
29-
parseUrlencoded = split (Pattern "&")
43+
parseUrlencoded :: Options -> String -> Either String (Array (Tuple String (Maybe String)))
44+
parseUrlencoded opts = split (Pattern "&")
3045
>>> Array.filter (_ /= "")
3146
>>> map (split (Pattern "="))
32-
>>> map toTuple
47+
>>> map (toTuple opts)
3348
>>> sequence

src/Hyper/Node/Server.purs

+5-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import Data.Newtype (unwrap)
2828
import Data.StrMap as StrMap
2929
import Data.Tuple (Tuple(..))
3030
import Hyper.Conn (Conn)
31+
import Hyper.Form.Urlencoded (Options) as Urlencoded
3132
import Hyper.Middleware (Middleware, evalMiddleware, lift')
3233
import Hyper.Middleware.Class (getConn, modifyConn)
3334
import Hyper.Node.Server.Options (Options)
@@ -210,14 +211,14 @@ instance responseWriterHttpResponse :: MonadAff (http ∷ HTTP | e) m
210211
:*> modifyConn (_ { response = HttpResponse r })
211212

212213

213-
mkHttpRequest :: HTTP.Request -> HttpRequest
214-
mkHttpRequest request =
214+
mkHttpRequest :: Urlencoded.Options -> HTTP.Request -> HttpRequest
215+
mkHttpRequest opts request =
215216
HttpRequest request requestData
216217
where
217218
headers = HTTP.requestHeaders request
218219
requestData =
219220
{ url: HTTP.requestURL request
220-
, parsedUrl: defer \_ -> parseUrl (HTTP.requestURL request)
221+
, parsedUrl: defer \_ -> parseUrl opts (HTTP.requestURL request)
221222
, headers: headers
222223
, method: Method.fromString (HTTP.requestMethod request)
223224
, contentLength: StrMap.lookup "content-length" headers
@@ -247,7 +248,7 @@ runServer' options components runM middleware = do
247248
where
248249
onRequest HTTP.Request HTTP.Response Eff (http :: HTTP | e) Unit
249250
onRequest request response =
250-
let conn = { request: mkHttpRequest request
251+
let conn = { request: mkHttpRequest {replacePlus: options.replacePlus} request
251252
, response: HttpResponse response
252253
, components: components
253254
}

src/Hyper/Node/Server/Options.purs

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Options e =
2424
, port :: Port
2525
, onListening :: Hostname -> Port -> Eff (http :: HTTP | e) Unit
2626
, onRequestError :: Error -> Eff (http :: HTTP | e) Unit
27+
, replacePlus :: Boolean
2728
}
2829

2930

@@ -33,6 +34,7 @@ defaultOptions =
3334
, port: Port 3000
3435
, onListening: const (const (pure unit))
3536
, onRequestError: const (pure unit)
37+
, replacePlus: true
3638
}
3739

3840

src/Hyper/Request.purs

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ module Hyper.Request
1212
) where
1313

1414
import Prelude
15+
1516
import Data.Array as Array
16-
import Data.String as String
1717
import Data.Bifunctor (lmap)
1818
import Data.Either (Either)
1919
import Data.HTTP.Method (CustomMethod, Method)
2020
import Data.Lazy (Lazy)
2121
import Data.Maybe (Maybe, fromMaybe)
2222
import Data.StrMap (StrMap)
23+
import Data.String as String
2324
import Data.Tuple (Tuple)
2425
import Hyper.Conn (Conn)
26+
import Hyper.Form.Urlencoded (Options) as Urlencoded
2527
import Hyper.Form.Urlencoded (parseUrlencoded)
2628
import Hyper.Middleware (Middleware)
2729

@@ -38,14 +40,14 @@ type ParsedUrl =
3840
, query :: Either String (Array (Tuple String (Maybe String)))
3941
}
4042

41-
parseUrl :: String -> ParsedUrl
42-
parseUrl url =
43+
parseUrl :: Urlencoded.Options -> String -> ParsedUrl
44+
parseUrl opts url =
4345
let
4446
idx = fromMaybe (String.length url) $ String.indexOf (String.Pattern "?") url
4547
rawPath = String.take idx url
4648
rawQuery = String.drop (idx + 1) url
4749
path = Array.filter (_ /= "") $ String.split (String.Pattern "/") rawPath
48-
query = lmap (const rawQuery) $ parseUrlencoded rawQuery
50+
query = lmap (const rawQuery) $ parseUrlencoded opts rawQuery
4951
in
5052
{path, query}
5153

src/Hyper/Test/TestServer.purs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
module Hyper.Test.TestServer where
22

3-
import Data.String as String
4-
import Data.StrMap as StrMap
53
import Control.Alt ((<|>))
64
import Control.Applicative (pure)
75
import Control.IxMonad (ipure, (:*>), (:>>=))
@@ -19,7 +17,10 @@ import Data.Monoid (mempty, class Monoid)
1917
import Data.Newtype (class Newtype, unwrap)
2018
import Data.Semigroup (class Semigroup, (<>))
2119
import Data.StrMap (StrMap)
20+
import Data.StrMap as StrMap
21+
import Data.String as String
2222
import Hyper.Conn (Conn)
23+
import Hyper.Form.Urlencoded (defaultOptions) as Urlencoded
2324
import Hyper.Header (Header)
2425
import Hyper.Middleware (lift')
2526
import Hyper.Middleware.Class (getConn, modifyConn)
@@ -56,7 +57,7 @@ instance requestTestRequest :: Monad m => Request TestRequest m where
5657
getRequestData =
5758
getConn :>>= \{ request: TestRequest r } ->
5859
ipure { url: r.url
59-
, parsedUrl: defer \_ -> parseUrl r.url
60+
, parsedUrl: defer \_ -> parseUrl Urlencoded.defaultOptions r.url
6061
, contentLength: Just (String.length r.body)
6162
, method: r.method
6263
, headers: r.headers

test/Hyper/FormSpec.purs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Hyper.FormSpec where
22

33
import Prelude
4+
45
import Control.Monad.Aff (Aff)
56
import Control.Monad.Eff.Exception (error)
67
import Control.Monad.Error.Class (throwError)
@@ -10,6 +11,7 @@ import Data.Maybe (Maybe(..), fromMaybe)
1011
import Data.StrMap (singleton)
1112
import Data.Tuple (Tuple(Tuple), fst)
1213
import Hyper.Form (Form(Form), parseForm)
14+
import Hyper.Form.Urlencoded (defaultOptions) as Urlencoded
1315
import Hyper.Middleware (runMiddleware)
1416
import Hyper.Test.TestServer (TestRequest(TestRequest))
1517
import Test.Spec (Spec, it, describe)
@@ -59,7 +61,7 @@ spec =
5961
where
6062
runParseForm body contentType =
6163
runMiddleware
62-
parseForm
64+
(parseForm Urlencoded.defaultOptions)
6365
{ request: TestRequest { method: Left GET
6466
, body: body
6567
, url: ""

test/Hyper/RequestSpec.purs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
module Hyper.RequestSpec where
22

33
import Prelude
4+
45
import Data.Either (Either(..))
56
import Data.Maybe (Maybe(..))
67
import Data.Tuple.Nested ((/\))
8+
import Hyper.Form.Urlencoded (defaultOptions) as Urlencoded
79
import Hyper.Request (parseUrl)
810
import Test.Spec (Spec, it, describe)
911
import Test.Spec.Assertions (shouldEqual)
@@ -12,21 +14,21 @@ spec :: forall e. Spec e Unit
1214
spec =
1315
describe "Hyper.Request" do
1416
it "parses the root URL" do
15-
let result = parseUrl "/"
17+
let result = parseUrl Urlencoded.defaultOptions "/"
1618
result.path `shouldEqual` []
1719
result.query `shouldEqual` Right []
1820

1921
it "parses non-root URLs" do
20-
let result = parseUrl "/foo/bar"
22+
let result = parseUrl Urlencoded.defaultOptions "/foo/bar"
2123
result.path `shouldEqual` ["foo", "bar"]
2224
result.query `shouldEqual` Right []
2325

2426
it "parses URLs with query strings" do
25-
let result = parseUrl "/foo/bar?abc=def=ghi"
27+
let result = parseUrl Urlencoded.defaultOptions "/foo/bar?abc=def=ghi"
2628
result.path `shouldEqual` ["foo", "bar"]
2729
result.query `shouldEqual` Left "abc=def=ghi"
2830

2931
it "parses URLs with formatted query strings" do
30-
let result = parseUrl "/foo/bar?abc=def&ghi"
32+
let result = parseUrl Urlencoded.defaultOptions "/foo/bar?abc=def&ghi"
3133
result.path `shouldEqual` ["foo", "bar"]
3234
result.query `shouldEqual` Right ["abc" /\ Just "def", "ghi" /\ Nothing]

0 commit comments

Comments
 (0)