on master help sign in
Merge pull request #4 from dlarsson/master

Support for matrix parameters

commit4eb943021fa03d79b084dc4bfb3a032b3001949a authorJulian Arni <jkarni@gmail.com> dateThu 15 Jan 2015, 10:52 (12 years ago) parent3e0be5a8e5bc6e
8 files changed+124−20
.gitignore+17 servant.cabal+2−1 src/Servant/API.hs+3 src/Servant/API/MatrixParam.hs+35 src/Servant/QQ.hs+30−17 src/Servant/Utils/Links.hs+7−1 test/Servant/QQSpec.hs+23 test/Servant/Utils/LinksSpec.hs+7−1
.gitignorenew file+17
@@ -0,0 +1,17 @@
1+dist
2+cabal-dev
3+*.o
4+*.hi
5+*.chi
6+*.chs.h
7+*.dyn_o
8+*.dyn_hi
9+.virtualenv
10+.hpc
11+.hsenv
12+.cabal-sandbox/
13+cabal.sandbox.config
14+cabal.config
15+*.prof
16+*.aux
17+*.hp
servant.cabal+2−1
@@ -1,5 +1,5 @@
11name: servant
2−version: 0.2.1
2+version: 0.2.2
33synopsis: A family of combinators for defining webservices APIs
44description:
55 A family of combinators for defining webservices APIs and serving them
@@ -34,6 +34,7 @@ library
3434 Servant.API.Post
3535 Servant.API.Put
3636 Servant.API.QueryParam
37+ Servant.API.MatrixParam
3738 Servant.API.Raw
3839 Servant.API.ReqBody
3940 Servant.API.Sub
src/Servant/API.hs+3
@@ -15,6 +15,8 @@ module Servant.API (
1515 module Servant.API.QueryParam,
1616 -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
1717 module Servant.API.ReqBody,
18+ -- | Retrieving matrix parameters from the 'URI' segment: @'MatrixParam'@
19+ module Servant.API.MatrixParam,
1820
1921 -- * Actual endpoints, distinguished by HTTP method
2022 -- | GET requests
@@ -45,6 +47,7 @@ import Servant.API.Header ( Header )
4547import Servant.API.Post ( Post )
4648import Servant.API.Put ( Put )
4749import Servant.API.QueryParam ( QueryFlag, QueryParams, QueryParam )
50+import Servant.API.MatrixParam ( MatrixFlag, MatrixParams, MatrixParam )
4851import Servant.API.Raw ( Raw )
4952import Servant.API.ReqBody ( ReqBody )
5053import Servant.API.Sub ( (:>)(..) )
src/Servant/API/MatrixParam.hsnew file+35
@@ -0,0 +1,35 @@
1+{-# LANGUAGE PolyKinds #-}
2+module Servant.API.MatrixParam where
3+
4+-- | Lookup the value associated to the @sym@ matrix string parameter
5+-- and try to extract it as a value of type @a@.
6+--
7+-- Example:
8+--
9+-- > -- /books;author=<author name>
10+-- > type MyApi = "books" :> MatrixParam "author" Text :> Get [Book]
11+data MatrixParam sym a
12+
13+-- | Lookup the values associated to the @sym@ matrix string parameter
14+-- and try to extract it as a value of type @[a]@. This is typically
15+-- meant to support matrix string parameters of the form
16+-- @param[]=val1;param[]=val2@ and so on. Note that servant doesn't actually
17+-- require the @[]@s and will fetch the values just fine with
18+-- @param=val1;param=val2@, too.
19+--
20+-- Example:
21+--
22+-- > -- /books;authors[]=<author1>;authors[]=<author2>;...
23+-- > type MyApi = "books" :> MatrixParams "authors" Text :> Get [Book]
24+data MatrixParams sym a
25+
26+-- | Lookup a potentially value-less matrix string parameter
27+-- with boolean semantics. If the param @sym@ is there without any value,
28+-- or if it's there with value "true" or "1", it's interpreted as 'True'.
29+-- Otherwise, it's interpreted as 'False'.
30+--
31+-- Example:
32+--
33+-- > -- /books;published
34+-- > type MyApi = "books" :> MatrixFlag "published" :> Get [Book]
35+data MatrixFlag sym
src/Servant/QQ.hs+30−17
@@ -29,7 +29,6 @@
2929module Servant.QQ (sitemap) where
3030
3131import Control.Monad ( void )
32−import Control.Applicative ( (<$>) )
3332import Language.Haskell.TH.Quote ( QuasiQuoter(..) )
3433import Language.Haskell.TH
3534 ( mkName, Type(AppT, ConT, LitT), TyLit(StrTyLit) )
@@ -58,6 +57,7 @@ import Servant.API.Post ( Post )
5857import Servant.API.Put ( Put )
5958import Servant.API.Delete ( Delete )
6059import Servant.API.QueryParam ( QueryParam )
60+import Servant.API.MatrixParam ( MatrixParam )
6161import Servant.API.ReqBody ( ReqBody )
6262import Servant.API.Sub ( (:>) )
6363import Servant.API.Alternative ( (:<|>) )
@@ -68,15 +68,16 @@ import Servant.API.Alternative ( (:<|>) )
6868-- only one of 'get', 'post', 'put', and 'delete' in a value), but
6969-- sometimes requires a little more work.
7070class ExpSYM repr' repr | repr -> repr', repr' -> repr where
71− lit :: String -> repr' -> repr
72− capture :: String -> String -> repr -> repr
73− reqBody :: String -> repr -> repr
74− queryParam :: String -> String -> repr -> repr
75− conj :: repr' -> repr -> repr
76− get :: String -> repr
77− post :: String -> repr
78− put :: String -> repr
79− delete :: String -> repr
71+ lit :: String -> repr' -> repr
72+ capture :: String -> String -> repr -> repr
73+ reqBody :: String -> repr -> repr
74+ queryParam :: String -> String -> repr -> repr
75+ matrixParam :: String -> String -> repr -> repr
76+ conj :: repr' -> repr -> repr
77+ get :: String -> repr
78+ post :: String -> repr
79+ put :: String -> repr
80+ delete :: String -> repr
8081
8182
8283infixr 6 >:
@@ -90,7 +91,9 @@ instance ExpSYM Type Type where
9091 capture name typ r = AppT (AppT (ConT ''Capture) (LitT (StrTyLit name)))
9192 (ConT $ mkName typ) >: r
9293 reqBody typ r = AppT (ConT ''ReqBody) (ConT $ mkName typ) >: r
93− queryParam name typ r = AppT (AppT (ConT ''QueryParam) (LitT (StrTyLit name)))
94+ queryParam name typ r = AppT (AppT (ConT ''QueryParam) (LitT (StrTyLit name)))
95+ (ConT $ mkName typ) >: r
96+ matrixParam name typ r = AppT (AppT (ConT ''MatrixParam) (LitT (StrTyLit name)))
9497 (ConT $ mkName typ) >: r
9598 conj x = AppT (AppT (ConT ''(:>)) x)
9699 get typ = AppT (ConT ''Get) (ConT $ mkName typ)
@@ -111,17 +114,27 @@ parseUrlSegment = try parseCapture
111114 <|> try parseLit
112115 where
113116 parseCapture = do
114− cname <- many (noneOf " ?/:")
117+ cname <- many (noneOf " ?/:;")
115118 char ':'
116− ctyp <- many (noneOf " ?/:")
117− return $ capture cname ctyp
119+ ctyp <- many (noneOf " ?/:;")
120+ mx <- many parseMatrixParam
121+ return $ capture cname ctyp . foldr (.) id mx
118122 parseQueryParam = do
119123 char '?'
120− cname <- many (noneOf " ?/:")
124+ cname <- many (noneOf " ?/:;")
121125 char ':'
122− ctyp <- many (noneOf " ?/:")
126+ ctyp <- many (noneOf " ?/:;")
123127 return $ queryParam cname ctyp
124− parseLit = lit <$> many (noneOf " ?/:")
128+ parseLit = do
129+ lt <- many (noneOf " ?/:;")
130+ mx <- many parseMatrixParam
131+ return $ lit lt . foldr (.) id mx
132+ parseMatrixParam = do
133+ char ';'
134+ cname <- many (noneOf " ?/:;")
135+ char ':'
136+ ctyp <- many (noneOf " ?/:;")
137+ return $ matrixParam cname ctyp
125138
126139parseUrl :: ExpSYM repr repr => Parser (repr -> repr)
127140parseUrl = do
src/Servant/Utils/Links.hs+7−1
@@ -53,7 +53,8 @@ import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
5353
5454import Servant.API.Capture ( Capture )
5555import Servant.API.ReqBody ( ReqBody )
56−import Servant.API.QueryParam ( QueryParam )
56+import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
57+import Servant.API.MatrixParam ( MatrixParam, MatrixParams, MatrixFlag )
5758import Servant.API.Get ( Get )
5859import Servant.API.Post ( Post )
5960import Servant.API.Put ( Put )
@@ -78,6 +79,11 @@ type family IsElem a s where
7879 IsElem (e :> sa) (Capture x y :> sb) = IsElem sa sb
7980 IsElem sa (ReqBody x :> sb) = IsElem sa sb
8081 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
82+ IsElem sa (QueryParams x y :> sb) = IsElem sa sb
83+ IsElem sa (QueryFlag x :> sb) = IsElem sa sb
84+ IsElem sa (MatrixParam x y :> sb) = IsElem sa sb
85+ IsElem sa (MatrixParams x y :> sb) = IsElem sa sb
86+ IsElem sa (MatrixFlag x :> sb) = IsElem sa sb
8187 IsElem e e = 'True
8288 IsElem e a = 'False
8389
test/Servant/QQSpec.hs+23
@@ -15,6 +15,7 @@ import Servant.API
1515 ( (:<|>),
1616 ReqBody,
1717 QueryParam,
18+ MatrixParam,
1819 Put,
1920 Get,
2021 Post,
@@ -85,6 +86,20 @@ type SimpleQueryParam' = "hello" :> QueryParam "p" Int :> Post Bool
8586type SimpleQueryParam'' = "hello" :> QueryParam "r" Int :> Post Bool
8687type SimpleQueryParam''' = "hello" :> QueryParam "p" Bool :> Post Bool
8788
89+type SimpleMatrixParam = [sitemap|
90+POST hello;p:Int Bool
91+|]
92+type SimpleMatrixParam' = "hello" :> MatrixParam "p" Int :> Post Bool
93+type SimpleMatrixParam'' = "hello" :> MatrixParam "r" Int :> Post Bool
94+type SimpleMatrixParam''' = "hello" :> MatrixParam "p" Bool :> Post Bool
95+
96+type ComplexMatrixParam = [sitemap|
97+POST hello;p:Int;q:String/world;r:Int Bool
98+|]
99+type ComplexMatrixParam' = "hello" :> MatrixParam "p" Int :> MatrixParam "q" String :> "world" :> MatrixParam "r" Int :> Post Bool
100+type ComplexMatrixParam'' = "hello" :> MatrixParam "p" Int :> MatrixParam "q" String :> "world" :> MatrixParam "s" Int :> Post Bool
101+type ComplexMatrixParam''' = "hello" :> MatrixParam "p" Int :> MatrixParam "q" String :> "world" :> MatrixParam "r" Bool :> Post Bool
102+
88103-- Combinations ----------------------------------------------------------
89104
90105type TwoPaths = [sitemap|
@@ -153,6 +168,14 @@ spec = do
153168 (u::SimpleQueryParam) ~= (u::SimpleQueryParam' ) ~> True
154169 (u::SimpleQueryParam) ~= (u::SimpleQueryParam'') ~> False
155170 (u::SimpleQueryParam) ~= (u::SimpleQueryParam''') ~> False
171+ it "Handles simple matrix parameters" $ do
172+ (u::SimpleMatrixParam) ~= (u::SimpleMatrixParam' ) ~> True
173+ (u::SimpleMatrixParam) ~= (u::SimpleMatrixParam'') ~> False
174+ (u::SimpleMatrixParam) ~= (u::SimpleMatrixParam''') ~> False
175+ it "Handles more complex matrix parameters" $ do
176+ (u::ComplexMatrixParam) ~= (u::ComplexMatrixParam' ) ~> True
177+ (u::ComplexMatrixParam) ~= (u::ComplexMatrixParam'') ~> False
178+ (u::ComplexMatrixParam) ~= (u::ComplexMatrixParam''') ~> False
156179 it "Handles multiples paths" $ do
157180 (u::TwoPaths) ~= (u::TwoPaths') ~> True
158181 it "Ignores inline comments" $ do
test/Servant/Utils/LinksSpec.hs+7−1
@@ -6,7 +6,7 @@ module Servant.Utils.LinksSpec where
66import Test.Hspec ( Spec, it, describe )
77
88import Servant.API
9− ( type (:<|>), ReqBody, QueryParam, Get, Post, Capture, type (:>) )
9+ ( type (:<|>), ReqBody, QueryParam, MatrixParam, MatrixParams, MatrixFlag, Get, Post, Capture, type (:>) )
1010import Servant.QQSpec ( (~>) )
1111import Servant.Utils.Links ( IsElem, IsLink )
1212
@@ -14,12 +14,15 @@ import Servant.Utils.Links ( IsElem, IsLink )
1414type TestApi =
1515 "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Get Bool
1616 :<|> "greet" :> ReqBody 'True :> Post Bool
17+ :<|> "parent" :> MatrixParams "name" String :> "child" :> MatrixParam "gender" String :> Get String
1718
1819type TestLink = "hello" :> "hi" :> Get Bool
1920type TestLink2 = "greet" :> Post Bool
21+type TestLink3 = "parent" :> "child" :> Get String
2022
2123type BadTestLink = "hallo" :> "hi" :> Get Bool
2224type BadTestLink2 = "greet" :> Get Bool
25+type BadTestLink3 = "parent" :> "child" :> MatrixFlag "male" :> Get String
2326
2427type NotALink = "hello" :> Capture "x" Bool :> Get Bool
2528type NotALink2 = "hello" :> ReqBody 'True :> Get Bool
@@ -39,15 +42,18 @@ isElem = describe "IsElem" $ do
3942 it "is True when the first argument is an url within the second" $ do
4043 reflected (Proxy::Proxy (IsElem TestLink TestApi)) ~> True
4144 reflected (Proxy::Proxy (IsElem TestLink2 TestApi)) ~> True
45+ reflected (Proxy::Proxy (IsElem TestLink3 TestApi)) ~> True
4246 it "is False when the first argument is not an url within the second" $ do
4347 reflected (Proxy::Proxy (IsElem BadTestLink TestApi)) ~> False
4448 reflected (Proxy::Proxy (IsElem BadTestLink2 TestApi)) ~> False
49+ reflected (Proxy::Proxy (IsElem BadTestLink3 TestApi)) ~> False
4550
4651isLink :: Spec
4752isLink = describe "IsLink" $ do
4853 it "is True when all Subs are paths and the last is a method" $ do
4954 reflected (Proxy::Proxy (IsLink TestLink)) ~> True
5055 reflected (Proxy::Proxy (IsLink TestLink2)) ~> True
56+ reflected (Proxy::Proxy (IsLink TestLink3)) ~> True
5157 it "is False of anything with captures" $ do
5258 reflected (Proxy::Proxy (IsLink NotALink)) ~> False
5359 reflected (Proxy::Proxy (IsLink NotALink2)) ~> False