Add tooling for working with query parameters (#69)
This commit is contained in:
parent
278e110d59
commit
0e71e9204a
37
docs/Examples/QueryParameters/Main.purs
Normal file
37
docs/Examples/QueryParameters/Main.purs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
module QueryParameters where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Monad.Eff.Console as Console
|
||||||
|
import HTTPure as HTTPure
|
||||||
|
import HTTPure ((!!))
|
||||||
|
|
||||||
|
-- | Serve the example server on this port
|
||||||
|
port :: Int
|
||||||
|
port = 8087
|
||||||
|
|
||||||
|
-- | Shortcut for `show port`
|
||||||
|
portS :: String
|
||||||
|
portS = show port
|
||||||
|
|
||||||
|
-- | Specify the routes
|
||||||
|
router :: forall e. HTTPure.Request -> HTTPure.ResponseM e
|
||||||
|
router { query }
|
||||||
|
| query !! "foo" /= "" = HTTPure.ok $ "foo"
|
||||||
|
| query !! "bar" == "test" = HTTPure.ok $ "bar"
|
||||||
|
| otherwise = HTTPure.ok $ query !! "baz"
|
||||||
|
|
||||||
|
-- | Boot up the server
|
||||||
|
main :: forall e. HTTPure.ServerM (console :: Console.CONSOLE | e)
|
||||||
|
main = HTTPure.serve port router do
|
||||||
|
Console.log $ " ┌────────────────────────────────────────┐"
|
||||||
|
Console.log $ " │ Server now up on port " <> portS <> " │"
|
||||||
|
Console.log $ " │ │"
|
||||||
|
Console.log $ " │ To test, run: │"
|
||||||
|
Console.log $ " │ > curl localhost:" <> portS <> "?foo │"
|
||||||
|
Console.log $ " │ # => foo │"
|
||||||
|
Console.log $ " │ > curl localhost:" <> portS <> "?bar=test │"
|
||||||
|
Console.log $ " │ # => bar │"
|
||||||
|
Console.log $ " │ > curl localhost:" <> portS <> "?baz=<anything> │"
|
||||||
|
Console.log $ " │ # => <anything> │"
|
||||||
|
Console.log $ " └────────────────────────────────────────┘"
|
12
docs/Examples/QueryParameters/Readme.md
Normal file
12
docs/Examples/QueryParameters/Readme.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Query Parameters Example
|
||||||
|
|
||||||
|
This is a basic example that demonstrates working with URL query parameters. It
|
||||||
|
includes an example of routing based on the _existence_ of a query parameter, an
|
||||||
|
example of routing based on the _value_ of a given query parameter, and an
|
||||||
|
example where the response is driven by the contents of a query parameter.
|
||||||
|
|
||||||
|
To run the example server, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make example EXAMPLE=QueryParameters
|
||||||
|
```
|
@ -8,6 +8,7 @@ import Prelude
|
|||||||
import Data.Array as Array
|
import Data.Array as Array
|
||||||
import Data.Maybe as Maybe
|
import Data.Maybe as Maybe
|
||||||
import Data.Monoid as Monoid
|
import Data.Monoid as Monoid
|
||||||
|
import Data.StrMap as StrMap
|
||||||
|
|
||||||
-- | Types that implement the Lookup class can be looked up by some key to
|
-- | Types that implement the Lookup class can be looked up by some key to
|
||||||
-- | retrieve some value. For instance, you could have an implementation for
|
-- | retrieve some value. For instance, you could have an implementation for
|
||||||
@ -24,3 +25,9 @@ infixl 8 lookup as !!
|
|||||||
-- | return a Maybe. If the index is out of bounds, the return value is mempty.
|
-- | return a Maybe. If the index is out of bounds, the return value is mempty.
|
||||||
instance lookupArray :: Monoid.Monoid m => Lookup (Array m) m Int where
|
instance lookupArray :: Monoid.Monoid m => Lookup (Array m) m Int where
|
||||||
lookup arr = Maybe.fromMaybe Monoid.mempty <<< Array.index arr
|
lookup arr = Maybe.fromMaybe Monoid.mempty <<< Array.index arr
|
||||||
|
|
||||||
|
-- | A default instance of Lookup for a StrMap of some type of Monoid. If the
|
||||||
|
-- | key does not exist in the StrMap, then the return value is mempty.
|
||||||
|
instance lookupStrMap :: Monoid.Monoid m =>
|
||||||
|
Lookup (StrMap.StrMap m) m String where
|
||||||
|
lookup strMap = Maybe.fromMaybe Monoid.mempty <<< flip StrMap.lookup strMap
|
||||||
|
@ -3,12 +3,36 @@ module HTTPure.Query
|
|||||||
, read
|
, read
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.Array as Array
|
||||||
|
import Data.Maybe as Maybe
|
||||||
|
import Data.String as String
|
||||||
import Data.StrMap as StrMap
|
import Data.StrMap as StrMap
|
||||||
|
import Data.Tuple as Tuple
|
||||||
import Node.HTTP as HTTP
|
import Node.HTTP as HTTP
|
||||||
|
|
||||||
|
-- | The Query type is a StrMap of Strings, with one entry per query parameter
|
||||||
|
-- | in the request. For any query parameters that don't have values
|
||||||
|
-- | (`/some/path?query`), the value in the StrMap for that parameter will be
|
||||||
|
-- | the string "true". Note that this type has an implementation of Lookup for
|
||||||
|
-- | `String` keys defined by `lookpStrMap` in `Lookup.purs` because
|
||||||
|
-- | `lookupStrMap` is defined for any `StrMap` of `Monoids`. So you can do
|
||||||
|
-- | something like `query !! "foo"` to get the value of the query parameter
|
||||||
|
-- | "foo".
|
||||||
type Query = StrMap.StrMap String
|
type Query = StrMap.StrMap String
|
||||||
|
|
||||||
-- | The StrMap of query segments in the given HTTP Request.
|
-- | The StrMap of query segments in the given HTTP Request.
|
||||||
-- | TODO fill in this stub
|
|
||||||
read :: HTTP.Request -> Query
|
read :: HTTP.Request -> Query
|
||||||
read _ = StrMap.empty
|
read =
|
||||||
|
HTTP.requestURL >>> split "?" >>> last >>> split "&" >>> nonempty >>> toStrMap
|
||||||
|
where
|
||||||
|
toStrMap = map toTuple >>> StrMap.fromFoldable
|
||||||
|
nonempty = Array.filter ((/=) "")
|
||||||
|
split = String.Pattern >>> String.split
|
||||||
|
first = Array.head >>> Maybe.fromMaybe ""
|
||||||
|
last = Array.tail >>> Maybe.fromMaybe [] >>> String.joinWith ""
|
||||||
|
toTuple item = Tuple.Tuple (first itemParts) $ value $ last itemParts
|
||||||
|
where
|
||||||
|
value val = if val == "" then "true" else val
|
||||||
|
itemParts = split "=" item
|
||||||
|
@ -13,6 +13,7 @@ import Headers as Headers
|
|||||||
import HelloWorld as HelloWorld
|
import HelloWorld as HelloWorld
|
||||||
import MultiRoute as MultiRoute
|
import MultiRoute as MultiRoute
|
||||||
import PathSegments as PathSegments
|
import PathSegments as PathSegments
|
||||||
|
import QueryParameters as QueryParameters
|
||||||
import Post as Post
|
import Post as Post
|
||||||
import SSL as SSL
|
import SSL as SSL
|
||||||
|
|
||||||
@ -50,6 +51,19 @@ pathSegmentsSpec = Spec.it "runs the path segments example" do
|
|||||||
somebars ?= "[\"some\",\"bars\"]"
|
somebars ?= "[\"some\",\"bars\"]"
|
||||||
where port = PathSegments.port
|
where port = PathSegments.port
|
||||||
|
|
||||||
|
queryParametersSpec :: SpecHelpers.Test
|
||||||
|
queryParametersSpec = Spec.it "runs the query parameters example" do
|
||||||
|
EffClass.liftEff QueryParameters.main
|
||||||
|
foo <- SpecHelpers.get port StrMap.empty "/?foo"
|
||||||
|
foo ?= "foo"
|
||||||
|
bar <- SpecHelpers.get port StrMap.empty "/?bar=test"
|
||||||
|
bar ?= "bar"
|
||||||
|
notbar <- SpecHelpers.get port StrMap.empty "/?bar=nottest"
|
||||||
|
notbar ?= ""
|
||||||
|
baz <- SpecHelpers.get port StrMap.empty "/?baz=test"
|
||||||
|
baz ?= "test"
|
||||||
|
where port = QueryParameters.port
|
||||||
|
|
||||||
postSpec :: SpecHelpers.Test
|
postSpec :: SpecHelpers.Test
|
||||||
postSpec = Spec.it "runs the post example" do
|
postSpec = Spec.it "runs the post example" do
|
||||||
EffClass.liftEff Post.main
|
EffClass.liftEff Post.main
|
||||||
@ -70,5 +84,6 @@ integrationSpec = Spec.describe "Integration" do
|
|||||||
helloWorldSpec
|
helloWorldSpec
|
||||||
multiRouteSpec
|
multiRouteSpec
|
||||||
pathSegmentsSpec
|
pathSegmentsSpec
|
||||||
|
queryParametersSpec
|
||||||
postSpec
|
postSpec
|
||||||
sslSpec
|
sslSpec
|
||||||
|
@ -2,6 +2,8 @@ module HTTPure.LookupSpec where
|
|||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
|
import Data.StrMap as StrMap
|
||||||
|
|
||||||
import Test.Spec as Spec
|
import Test.Spec as Spec
|
||||||
|
|
||||||
import HTTPure.Lookup ((!!))
|
import HTTPure.Lookup ((!!))
|
||||||
@ -18,6 +20,18 @@ lookupArraySpec = Spec.describe "lookupArray" do
|
|||||||
Spec.it "is an empty monoid" do
|
Spec.it "is an empty monoid" do
|
||||||
[ "one", "two", "three" ] !! 4 ?= ""
|
[ "one", "two", "three" ] !! 4 ?= ""
|
||||||
|
|
||||||
|
lookupStrMapSpec :: SpecHelpers.Test
|
||||||
|
lookupStrMapSpec = Spec.describe "lookupStrMap" do
|
||||||
|
Spec.describe "when the key is in the StrMap" do
|
||||||
|
Spec.it "is the value at the given key" do
|
||||||
|
mockStrMap !! "foo" ?= "bar"
|
||||||
|
Spec.describe "when the key is not in the StrMap" do
|
||||||
|
Spec.it "is an empty monoid" do
|
||||||
|
mockStrMap !! "baz" ?= ""
|
||||||
|
where
|
||||||
|
mockStrMap = StrMap.singleton "foo" "bar"
|
||||||
|
|
||||||
lookupSpec :: SpecHelpers.Test
|
lookupSpec :: SpecHelpers.Test
|
||||||
lookupSpec = Spec.describe "Lookup" do
|
lookupSpec = Spec.describe "Lookup" do
|
||||||
lookupArraySpec
|
lookupArraySpec
|
||||||
|
lookupStrMapSpec
|
||||||
|
@ -3,6 +3,7 @@ module HTTPure.QuerySpec where
|
|||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
import Data.StrMap as StrMap
|
import Data.StrMap as StrMap
|
||||||
|
import Data.Tuple as Tuple
|
||||||
import Test.Spec as Spec
|
import Test.Spec as Spec
|
||||||
|
|
||||||
import HTTPure.Query as Query
|
import HTTPure.Query as Query
|
||||||
@ -12,9 +13,42 @@ import HTTPure.SpecHelpers ((?=))
|
|||||||
|
|
||||||
readSpec :: SpecHelpers.Test
|
readSpec :: SpecHelpers.Test
|
||||||
readSpec = Spec.describe "read" do
|
readSpec = Spec.describe "read" do
|
||||||
Spec.it "is always an empty StrMap (until the stub is fully implemented)" do
|
Spec.describe "with no query string" do
|
||||||
req <- SpecHelpers.mockRequest "" "" "" []
|
Spec.it "is an empty StrMap" do
|
||||||
|
req <- SpecHelpers.mockRequest "" "/test" "" []
|
||||||
Query.read req ?= StrMap.empty
|
Query.read req ?= StrMap.empty
|
||||||
|
Spec.describe "with an empty query string" do
|
||||||
|
Spec.it "is an empty StrMap" do
|
||||||
|
req <- SpecHelpers.mockRequest "" "/test?" "" []
|
||||||
|
Query.read req ?= StrMap.empty
|
||||||
|
Spec.describe "with a query parameter in the query string" do
|
||||||
|
Spec.it "is a correct StrMap" do
|
||||||
|
req <- SpecHelpers.mockRequest "" "/test?a=b" "" []
|
||||||
|
Query.read req ?= StrMap.singleton "a" "b"
|
||||||
|
Spec.describe "with empty fields in the query string" do
|
||||||
|
Spec.it "ignores the empty fields" do
|
||||||
|
req <- SpecHelpers.mockRequest "" "/test?&&a=b&&" "" []
|
||||||
|
Query.read req ?= StrMap.singleton "a" "b"
|
||||||
|
Spec.describe "with duplicated params" do
|
||||||
|
Spec.it "takes the last param value" do
|
||||||
|
req <- SpecHelpers.mockRequest "" "/test?a=b&a=c" "" []
|
||||||
|
Query.read req ?= StrMap.singleton "a" "c"
|
||||||
|
Spec.describe "with empty params" do
|
||||||
|
Spec.it "uses 'true' as the value" do
|
||||||
|
req <- SpecHelpers.mockRequest "" "/test?a" "" []
|
||||||
|
Query.read req ?= StrMap.singleton "a" "true"
|
||||||
|
Spec.describe "with complex params" do
|
||||||
|
Spec.it "is the correct StrMap" do
|
||||||
|
req <- SpecHelpers.mockRequest "" "/test?&&a&b=c&b=d&&&e=f&g=&" "" []
|
||||||
|
Query.read req ?= expectedComplexResult
|
||||||
|
where
|
||||||
|
expectedComplexResult =
|
||||||
|
StrMap.fromFoldable
|
||||||
|
[ Tuple.Tuple "a" "true"
|
||||||
|
, Tuple.Tuple "b" "d"
|
||||||
|
, Tuple.Tuple "e" "f"
|
||||||
|
, Tuple.Tuple "g" "true"
|
||||||
|
]
|
||||||
|
|
||||||
querySpec :: SpecHelpers.Test
|
querySpec :: SpecHelpers.Test
|
||||||
querySpec = Spec.describe "Query" do
|
querySpec = Spec.describe "Query" do
|
||||||
|
@ -23,7 +23,7 @@ fromHTTPRequestSpec = Spec.describe "fromHTTPRequest" do
|
|||||||
mock.path ?= [ "test" ]
|
mock.path ?= [ "test" ]
|
||||||
Spec.it "contains the correct query" do
|
Spec.it "contains the correct query" do
|
||||||
mock <- mockRequest
|
mock <- mockRequest
|
||||||
mock.query ?= StrMap.empty
|
mock.query ?= StrMap.singleton "a" "b"
|
||||||
Spec.it "contains the correct headers" do
|
Spec.it "contains the correct headers" do
|
||||||
mock <- mockRequest
|
mock <- mockRequest
|
||||||
mock.headers ?= Headers.headers mockHeaders
|
mock.headers ?= Headers.headers mockHeaders
|
||||||
|
Loading…
Reference in New Issue
Block a user