git.iko.soy/typeable/servant
on master help sign in
Merge pull request #4 from dlarsson/master
4eb9430Julian Arni
.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 @@
1name: servant
1name: servant
2−version: 0.2.1
2+version: 0.2.2
3synopsis: A family of combinators for defining webservices APIs
3synopsis: A family of combinators for defining webservices APIs
4description:
4description:
5 A family of combinators for defining webservices APIs and serving them
5 A family of combinators for defining webservices APIs and serving them
@@ -34,6 +34,7 @@ library
34 Servant.API.Post
34 Servant.API.Post
35 Servant.API.Put
35 Servant.API.Put
36 Servant.API.QueryParam
36 Servant.API.QueryParam
37+ Servant.API.MatrixParam
37 Servant.API.Raw
38 Servant.API.Raw
38 Servant.API.ReqBody
39 Servant.API.ReqBody
39 Servant.API.Sub
40 Servant.API.Sub
src/Servant/API.hs+3
@@ -15,6 +15,8 @@ module Servant.API (
15 module Servant.API.QueryParam,
15 module Servant.API.QueryParam,
16 -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
16 -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
17 module Servant.API.ReqBody,
17 module Servant.API.ReqBody,
18+ -- | Retrieving matrix parameters from the 'URI' segment: @'MatrixParam'@
19+ module Servant.API.MatrixParam,
18
20
19 -- * Actual endpoints, distinguished by HTTP method
21 -- * Actual endpoints, distinguished by HTTP method
20 -- | GET requests
22 -- | GET requests
@@ -45,6 +47,7 @@ import Servant.API.Header ( Header )
45import Servant.API.Post ( Post )
47import Servant.API.Post ( Post )
46import Servant.API.Put ( Put )
48import Servant.API.Put ( Put )
47import Servant.API.QueryParam ( QueryFlag, QueryParams, QueryParam )
49import Servant.API.QueryParam ( QueryFlag, QueryParams, QueryParam )
50+import Servant.API.MatrixParam ( MatrixFlag, MatrixParams, MatrixParam )
48import Servant.API.Raw ( Raw )
51import Servant.API.Raw ( Raw )
49import Servant.API.ReqBody ( ReqBody )
52import Servant.API.ReqBody ( ReqBody )
50import Servant.API.Sub ( (:>)(..) )
53import 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 @@
29module Servant.QQ (sitemap) where
29module Servant.QQ (sitemap) where
30
30
31import Control.Monad ( void )
31import Control.Monad ( void )
32−import Control.Applicative ( (<$>) )
33import Language.Haskell.TH.Quote ( QuasiQuoter(..) )
32import Language.Haskell.TH.Quote ( QuasiQuoter(..) )
34import Language.Haskell.TH
33import Language.Haskell.TH
35 ( mkName, Type(AppT, ConT, LitT), TyLit(StrTyLit) )
34 ( mkName, Type(AppT, ConT, LitT), TyLit(StrTyLit) )
@@ -58,6 +57,7 @@ import Servant.API.Post ( Post )
58import Servant.API.Put ( Put )
57import Servant.API.Put ( Put )
59import Servant.API.Delete ( Delete )
58import Servant.API.Delete ( Delete )
60import Servant.API.QueryParam ( QueryParam )
59import Servant.API.QueryParam ( QueryParam )
60+import Servant.API.MatrixParam ( MatrixParam )
61import Servant.API.ReqBody ( ReqBody )
61import Servant.API.ReqBody ( ReqBody )
62import Servant.API.Sub ( (:>) )
62import Servant.API.Sub ( (:>) )
63import Servant.API.Alternative ( (:<|>) )
63import Servant.API.Alternative ( (:<|>) )
@@ -68,15 +68,16 @@ import Servant.API.Alternative ( (:<|>) )
68-- only one of 'get', 'post', 'put', and 'delete' in a value), but
68-- only one of 'get', 'post', 'put', and 'delete' in a value), but
69-- sometimes requires a little more work.
69-- sometimes requires a little more work.
70class ExpSYM repr' repr | repr -> repr', repr' -> repr where
70class ExpSYM repr' repr | repr -> repr', repr' -> repr where
71− lit :: String -> repr' -> repr
71+ lit :: String -> repr' -> repr
72− capture :: String -> String -> repr -> repr
72+ capture :: String -> String -> repr -> repr
73− reqBody :: String -> repr -> repr
73+ reqBody :: String -> repr -> repr
74+ queryParam :: String -> String -> repr -> repr
74− queryParam :: String -> String -> repr -> repr
75+ matrixParam :: String -> String -> repr -> repr
75− conj :: repr' -> repr -> repr
76+ conj :: repr' -> repr -> repr
76− get :: String -> repr
77+ get :: String -> repr
77− post :: String -> repr
78+ post :: String -> repr
78− put :: String -> repr
79+ put :: String -> repr
79− delete :: String -> repr
80+ delete :: String -> repr
80
81
81
82
82infixr 6 >:
83infixr 6 >:
@@ -90,7 +91,9 @@ instance ExpSYM Type Type where
90 capture name typ r = AppT (AppT (ConT ''Capture) (LitT (StrTyLit name)))
91 capture name typ r = AppT (AppT (ConT ''Capture) (LitT (StrTyLit name)))
91 (ConT $ mkName typ) >: r
92 (ConT $ mkName typ) >: r
92 reqBody typ r = AppT (ConT ''ReqBody) (ConT $ mkName typ) >: r
93 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)))
94 (ConT $ mkName typ) >: r
97 (ConT $ mkName typ) >: r
95 conj x = AppT (AppT (ConT ''(:>)) x)
98 conj x = AppT (AppT (ConT ''(:>)) x)
96 get typ = AppT (ConT ''Get) (ConT $ mkName typ)
99 get typ = AppT (ConT ''Get) (ConT $ mkName typ)
@@ -111,17 +114,27 @@ parseUrlSegment = try parseCapture
111 <|> try parseLit
114 <|> try parseLit
112 where
115 where
113 parseCapture = do
116 parseCapture = do
114− cname <- many (noneOf " ?/:")
117+ cname <- many (noneOf " ?/:;")
115 char ':'
118 char ':'
116− ctyp <- many (noneOf " ?/:")
119+ ctyp <- many (noneOf " ?/:;")
120+ mx <- many parseMatrixParam
117− return $ capture cname ctyp
121+ return $ capture cname ctyp . foldr (.) id mx
118 parseQueryParam = do
122 parseQueryParam = do
119 char '?'
123 char '?'
120− cname <- many (noneOf " ?/:")
124+ cname <- many (noneOf " ?/:;")
121 char ':'
125 char ':'
122− ctyp <- many (noneOf " ?/:")
126+ ctyp <- many (noneOf " ?/:;")
123 return $ queryParam cname ctyp
127 return $ queryParam cname ctyp
128+ parseLit = do
129+ lt <- many (noneOf " ?/:;")
130+ mx <- many parseMatrixParam
131+ return $ lit lt . foldr (.) id mx
132+ parseMatrixParam = do
133+ char ';'
124− parseLit = lit <$> many (noneOf " ?/:")
134+ cname <- many (noneOf " ?/:;")
135+ char ':'
136+ ctyp <- many (noneOf " ?/:;")
137+ return $ matrixParam cname ctyp
125
138
126parseUrl :: ExpSYM repr repr => Parser (repr -> repr)
139parseUrl :: ExpSYM repr repr => Parser (repr -> repr)
127parseUrl = do
140parseUrl = do
src/Servant/Utils/Links.hs+7−1
@@ -53,7 +53,8 @@ import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
53
53
54import Servant.API.Capture ( Capture )
54import Servant.API.Capture ( Capture )
55import Servant.API.ReqBody ( ReqBody )
55import 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 )
57import Servant.API.Get ( Get )
58import Servant.API.Get ( Get )
58import Servant.API.Post ( Post )
59import Servant.API.Post ( Post )
59import Servant.API.Put ( Put )
60import Servant.API.Put ( Put )
@@ -78,6 +79,11 @@ type family IsElem a s where
78 IsElem (e :> sa) (Capture x y :> sb) = IsElem sa sb
79 IsElem (e :> sa) (Capture x y :> sb) = IsElem sa sb
79 IsElem sa (ReqBody x :> sb) = IsElem sa sb
80 IsElem sa (ReqBody x :> sb) = IsElem sa sb
80 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
81 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
81 IsElem e e = 'True
87 IsElem e e = 'True
82 IsElem e a = 'False
88 IsElem e a = 'False
83
89
test/Servant/QQSpec.hs+23
@@ -15,6 +15,7 @@ import Servant.API
15 ( (:<|>),
15 ( (:<|>),
16 ReqBody,
16 ReqBody,
17 QueryParam,
17 QueryParam,
18+ MatrixParam,
18 Put,
19 Put,
19 Get,
20 Get,
20 Post,
21 Post,
@@ -85,6 +86,20 @@ type SimpleQueryParam' = "hello" :> QueryParam "p" Int :> Post Bool
85type SimpleQueryParam'' = "hello" :> QueryParam "r" Int :> Post Bool
86type SimpleQueryParam'' = "hello" :> QueryParam "r" Int :> Post Bool
86type SimpleQueryParam''' = "hello" :> QueryParam "p" Bool :> Post Bool
87type SimpleQueryParam''' = "hello" :> QueryParam "p" Bool :> Post Bool
87
88
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+
88-- Combinations ----------------------------------------------------------
103-- Combinations ----------------------------------------------------------
89
104
90type TwoPaths = [sitemap|
105type TwoPaths = [sitemap|
@@ -153,6 +168,14 @@ spec = do
153 (u::SimpleQueryParam) ~= (u::SimpleQueryParam' ) ~> True
168 (u::SimpleQueryParam) ~= (u::SimpleQueryParam' ) ~> True
154 (u::SimpleQueryParam) ~= (u::SimpleQueryParam'') ~> False
169 (u::SimpleQueryParam) ~= (u::SimpleQueryParam'') ~> False
155 (u::SimpleQueryParam) ~= (u::SimpleQueryParam''') ~> False
170 (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
156 it "Handles multiples paths" $ do
179 it "Handles multiples paths" $ do
157 (u::TwoPaths) ~= (u::TwoPaths') ~> True
180 (u::TwoPaths) ~= (u::TwoPaths') ~> True
158 it "Ignores inline comments" $ do
181 it "Ignores inline comments" $ do
test/Servant/Utils/LinksSpec.hs+7−1
@@ -6,7 +6,7 @@ module Servant.Utils.LinksSpec where
6import Test.Hspec ( Spec, it, describe )
6import Test.Hspec ( Spec, it, describe )
7
7
8import Servant.API
8import Servant.API
9− ( type (:<|>), ReqBody, QueryParam, Get, Post, Capture, type (:>) )
9+ ( type (:<|>), ReqBody, QueryParam, MatrixParam, MatrixParams, MatrixFlag, Get, Post, Capture, type (:>) )
10import Servant.QQSpec ( (~>) )
10import Servant.QQSpec ( (~>) )
11import Servant.Utils.Links ( IsElem, IsLink )
11import Servant.Utils.Links ( IsElem, IsLink )
12
12
@@ -14,12 +14,15 @@ import Servant.Utils.Links ( IsElem, IsLink )
14type TestApi =
14type TestApi =
15 "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Get Bool
15 "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Get Bool
16 :<|> "greet" :> ReqBody 'True :> Post Bool
16 :<|> "greet" :> ReqBody 'True :> Post Bool
17+ :<|> "parent" :> MatrixParams "name" String :> "child" :> MatrixParam "gender" String :> Get String
17
18
18type TestLink = "hello" :> "hi" :> Get Bool
19type TestLink = "hello" :> "hi" :> Get Bool
19type TestLink2 = "greet" :> Post Bool
20type TestLink2 = "greet" :> Post Bool
21+type TestLink3 = "parent" :> "child" :> Get String
20
22
21type BadTestLink = "hallo" :> "hi" :> Get Bool
23type BadTestLink = "hallo" :> "hi" :> Get Bool
22type BadTestLink2 = "greet" :> Get Bool
24type BadTestLink2 = "greet" :> Get Bool
25+type BadTestLink3 = "parent" :> "child" :> MatrixFlag "male" :> Get String
23
26
24type NotALink = "hello" :> Capture "x" Bool :> Get Bool
27type NotALink = "hello" :> Capture "x" Bool :> Get Bool
25type NotALink2 = "hello" :> ReqBody 'True :> Get Bool
28type NotALink2 = "hello" :> ReqBody 'True :> Get Bool
@@ -39,15 +42,18 @@ isElem = describe "IsElem" $ do
39 it "is True when the first argument is an url within the second" $ do
42 it "is True when the first argument is an url within the second" $ do
40 reflected (Proxy::Proxy (IsElem TestLink TestApi)) ~> True
43 reflected (Proxy::Proxy (IsElem TestLink TestApi)) ~> True
41 reflected (Proxy::Proxy (IsElem TestLink2 TestApi)) ~> True
44 reflected (Proxy::Proxy (IsElem TestLink2 TestApi)) ~> True
45+ reflected (Proxy::Proxy (IsElem TestLink3 TestApi)) ~> True
42 it "is False when the first argument is not an url within the second" $ do
46 it "is False when the first argument is not an url within the second" $ do
43 reflected (Proxy::Proxy (IsElem BadTestLink TestApi)) ~> False
47 reflected (Proxy::Proxy (IsElem BadTestLink TestApi)) ~> False
44 reflected (Proxy::Proxy (IsElem BadTestLink2 TestApi)) ~> False
48 reflected (Proxy::Proxy (IsElem BadTestLink2 TestApi)) ~> False
49+ reflected (Proxy::Proxy (IsElem BadTestLink3 TestApi)) ~> False
45
50
46isLink :: Spec
51isLink :: Spec
47isLink = describe "IsLink" $ do
52isLink = describe "IsLink" $ do
48 it "is True when all Subs are paths and the last is a method" $ do
53 it "is True when all Subs are paths and the last is a method" $ do
49 reflected (Proxy::Proxy (IsLink TestLink)) ~> True
54 reflected (Proxy::Proxy (IsLink TestLink)) ~> True
50 reflected (Proxy::Proxy (IsLink TestLink2)) ~> True
55 reflected (Proxy::Proxy (IsLink TestLink2)) ~> True
56+ reflected (Proxy::Proxy (IsLink TestLink3)) ~> True
51 it "is False of anything with captures" $ do
57 it "is False of anything with captures" $ do
52 reflected (Proxy::Proxy (IsLink NotALink)) ~> False
58 reflected (Proxy::Proxy (IsLink NotALink)) ~> False
53 reflected (Proxy::Proxy (IsLink NotALink2)) ~> False
59 reflected (Proxy::Proxy (IsLink NotALink2)) ~> False