git.iko.soy/typeable/servant
on master help sign in
Rewrite Utils.Links to export a nicer API.
70ffe01Christian Marie
.travis.yml+4
@@ -3,6 +3,10 @@ language: haskell
3ghc:
3ghc:
4 - 7.8
4 - 7.8
5
5
6+after_success:
7+ - sudo apt-get -y install doctest
8+ - ./test-docs.sh
9+
6notifications:
10notifications:
7 irc:
11 irc:
8 channels:
12 channels:
servant.cabal+5
@@ -47,6 +47,8 @@ library
47 , template-haskell
47 , template-haskell
48 , parsec >= 3.1
48 , parsec >= 3.1
49 , string-conversions >= 0.3
49 , string-conversions >= 0.3
50+ , network-uri >= 2.6
51+ , safe
50 hs-source-dirs: src
52 hs-source-dirs: src
51 default-language: Haskell2010
53 default-language: Haskell2010
52 ghc-options: -Wall
54 ghc-options: -Wall
@@ -60,8 +62,11 @@ test-suite spec
60 main-is: Spec.hs
62 main-is: Spec.hs
61 build-depends:
63 build-depends:
62 base == 4.*
64 base == 4.*
65+ , deepseq
63 , hspec == 2.*
66 , hspec == 2.*
64 , QuickCheck
67 , QuickCheck
68+ , network-uri
69+ , parsec
65 , parsec
70 , parsec
66 , servant
71 , servant
67 , string-conversions
72 , string-conversions
src/Servant/API.hs+2−2
@@ -35,7 +35,7 @@ module Servant.API (
35 -- * Utilities
35 -- * Utilities
36 -- | QuasiQuotes for endpoints
36 -- | QuasiQuotes for endpoints
37 module Servant.QQ,
37 module Servant.QQ,
38− -- | Type-safe internal URLs
38+ -- | Type-safe internal URIs
39 module Servant.Utils.Links,
39 module Servant.Utils.Links,
40 ) where
40 ) where
41
41
@@ -52,4 +52,4 @@ import Servant.API.Raw ( Raw )
52import Servant.API.ReqBody ( ReqBody )
52import Servant.API.ReqBody ( ReqBody )
53import Servant.API.Sub ( (:>)(..) )
53import Servant.API.Sub ( (:>)(..) )
54import Servant.QQ ( sitemap )
54import Servant.QQ ( sitemap )
55−import Servant.Utils.Links ( mkLink )
55+import Servant.Utils.Links ( safeLink, URI(..) )
src/Servant/Utils/Links.hs+261−86
@@ -8,76 +8,102 @@
8{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE UndecidableInstances #-}
9{-# LANGUAGE UndecidableInstances #-}
10
10
11−-- | Type safe internal links.
11+-- | Type safe generation of internal links.
12--
12--
13−-- Provides the function 'mkLink':
13+-- Given an API with a few endpoints:
14--
14--
15−-- @
15+-- >>> :set -XDataKinds -XTypeFamilies -XTypeOperators
16−-- {-# LANGUAGE DataKinds #-}
16+-- >>> import Servant.API
17−-- {-# LANGUAGE TypeFamilies #-}
17+-- >>> import Servant.Utils.Links
18−-- {-# LANGUAGE TypeOperators #-}
18+-- >>> import Data.Proxy
19+-- >>>
20+-- >>>
21+-- >>>
22+-- >>> type Hello = "hello" :> Get Int
23+-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete
24+-- >>> type API = Hello :<|> Bye
25+-- >>> let api = Proxy :: Proxy API
19--
26--
20−-- import Servant.API
27+-- It is possible to generate links that are guaranteed to be within 'API' with
21−-- import Servant.Utils.Links
28+-- 'safeLink'.
22--
29--
23−-- -- You might want to put some custom types in your API
30+-- The first argument to 'safeLink' is a symbol representing the endpoint you
24−-- data Thing = Thing
31+-- would like to point to. This will need to end in a verb like Get, or Post.
25--
32--
26−-- -- If you want these to form part of a valid URL, just add them to the
33+-- You may omit 'QueryParam's and the like should you not want to provide them,
27−-- -- open type family IsElem' like so:
34+-- but certain other types like 'Capture' must be included.
28−-- type instance IsElem' e (Thing :> s) = IsElem e s
29--
35--
30−-- type API = "hello" :> Thing :> Get Int
36+-- The reason you may want to omit 'QueryParam's is that safeLink is a bit
31−-- :<|> "bye" :> QueryParam "name" String :> Post Bool
37+-- magical: if parameters are included that could take input it will return a
38+-- function that accepts that input and generates a link.
32--
39--
33−-- api :: API
40+-- This is best shown with an example. Here, a link is generated with no
34−-- api = undefined
41+-- parameters:
35--
42--
36−-- link1 :: "hello" :> Get Int
43+-- >>> let hello = Proxy :: Proxy ("hello" :> Get Int)
37−-- link1 = undefined
44+-- >>> print $ safeLink hello api
45+-- hello
38--
46--
39−-- link2 :: "hello" :> Delete
47+-- If the API has an endpoint with parameters then we can generate links with
40−-- link2 = undefined
48+-- or without those:
41--
49--
42−-- main :: IO ()
50+-- >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete)
43−-- main =
51+-- >>> print $ safeLink with api "Hubert"
44−-- -- typechecks, prints "/hello"'
52+-- bye?name=Hubert
45−-- let Link str = mkLink link1 api
46−-- in putStrLn str
47--
53--
48−-- -- doesn't typecheck
54+-- >>> let without = Proxy :: Proxy ("bye" :> Delete)
49−-- -- mkLink link2 api
55+-- >>> print $ safeLink without api
50−-- @
56+-- bye
51--
57--
52−-- That is, 'mkLink' takes two arguments, a link and a sitemap, and
58+-- Attempting to construct a link to an endpoint that does not exist in api
53−-- returns a 'Link', but only typechecks if the link proxy is a valid link,
59+-- will result in a type error like this:
54−-- and part of the sitemap.
55--
60--
56−-- __N.B.:__ 'mkLink' assumes a capture matches any string (without slashes).
61+-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete)
62+-- >>> safeLink bad_link api
63+-- <BLANKLINE>
64+-- <interactive>:56:1:
65+-- Could not deduce (Or
66+-- (IsElem' Delete (Get Int))
67+-- (IsElem'
68+-- ("hello" :> Delete)
69+-- ("bye" :> (QueryParam "name" String :> Delete))))
70+-- arising from a use of ‘safeLink’
71+-- In the expression: safeLink bad_link api
72+-- In an equation for ‘it’: it = safeLink bad_link api
73+--
74+-- This error is essentially saying that the type family couldn't find
75+-- bad_link under api after trying the open (but empty) type family
76+-- `IsElem'` as a last resort.
57module Servant.Utils.Links (
77module Servant.Utils.Links (
58− -- * Link and mkLink
78+ -- * Building and using safe links
59− -- | The only end-user utilities
79+ --
60− mkLink
80+ -- | Note that 'URI' is Network.URI.URI from the network-uri package.
61− , Link, unLink
81+ safeLink
62− -- * Internal
82+ , URI(..)
63− -- | These functions will likely only be of interest if you are writing
83+ -- * Adding custom types
64− -- more API combinators and would like to extend the behavior of
84+ , HasLink(..)
65− -- 'mkLink'
85+ , linkURI
66− , ValidLinkIn()
86+ , Link
67− , VLinkHelper(..)
87+ , IsElem'
68− , IsElem
88+ -- * Illustrative exports
69− , IsElem'
89+ , IsElem
70− , IsLink
90+ , Or
71− ) where
91+) where
72
92
93+import Data.List
73import Data.Proxy ( Proxy(..) )
94import Data.Proxy ( Proxy(..) )
95+import Data.Text (Text, unpack)
96+import Data.Monoid ( Monoid(..), (<>) )
97+import Network.URI ( URI(..), escapeURIString, isUnreserved )
74−import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
98+import GHC.TypeLits ( KnownSymbol, symbolVal )
75import GHC.Exts(Constraint)
99import GHC.Exts(Constraint)
76
100
101+import Servant.Common.Text
77import Servant.API.Capture ( Capture )
102import Servant.API.Capture ( Capture )
78import Servant.API.ReqBody ( ReqBody )
103import Servant.API.ReqBody ( ReqBody )
79import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
104import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
80import Servant.API.MatrixParam ( MatrixParam, MatrixParams, MatrixFlag )
105import Servant.API.MatrixParam ( MatrixParam, MatrixParams, MatrixFlag )
106+import Servant.API.Header ( Header )
81import Servant.API.Get ( Get )
107import Servant.API.Get ( Get )
82import Servant.API.Post ( Post )
108import Servant.API.Post ( Post )
83import Servant.API.Put ( Put )
109import Servant.API.Put ( Put )
@@ -86,20 +112,27 @@ import Servant.API.Sub ( type (:>) )
86import Servant.API.Raw ( Raw )
112import Servant.API.Raw ( Raw )
87import Servant.API.Alternative ( type (:<|>) )
113import Servant.API.Alternative ( type (:<|>) )
88
114
89−
115+-- | If either a or b produce an empty constraint, produce an empty constraint.
90type family Or (a :: Constraint) (b :: Constraint) :: Constraint where
116type family Or (a :: Constraint) (b :: Constraint) :: Constraint where
91 Or () b = ()
117 Or () b = ()
92 Or a () = ()
118 Or a () = ()
93
119
94−type family And (a :: Constraint) (b :: Constraint) :: Constraint where
120+-- | You may use this type family to tell the type checker that your custom type
95− And () () = ()
121+-- is a valid part of a link like so:
96−
122+--
123+-- Not that 'IsElem' is called, which mutually recurses back to `IsElem'`
124+--
125+-- >>> data CustomThing
126+-- >>> type instance IsElem' e (CustomThing :> s) = IsElem e s
127+--
128+-- Now you can add a HasLink instance and you are ready to go.
97type family IsElem' a s :: Constraint
129type family IsElem' a s :: Constraint
98
130
131+-- | Closed type family, check if endpoint is within api
99−type family IsElem a s :: Constraint where
132+type family IsElem endpoint api :: Constraint where
100 IsElem e (sa :<|> sb) = Or (IsElem e sa) (IsElem e sb)
133 IsElem e (sa :<|> sb) = Or (IsElem e sa) (IsElem e sb)
101 IsElem (e :> sa) (e :> sb) = IsElem sa sb
134 IsElem (e :> sa) (e :> sb) = IsElem sa sb
102− IsElem (e :> sa) (Capture x y :> sb) = IsElem sa sb
135+ IsElem sa (Header x :> sb) = IsElem sa sb
103 IsElem sa (ReqBody x :> sb) = IsElem sa sb
136 IsElem sa (ReqBody x :> sb) = IsElem sa sb
104 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
137 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
105 IsElem sa (QueryParams x y :> sb) = IsElem sa sb
138 IsElem sa (QueryParams x y :> sb) = IsElem sa sb
@@ -110,45 +143,187 @@ type family IsElem a s :: Constraint where
110 IsElem e e = ()
143 IsElem e e = ()
111 IsElem e a = IsElem' e a
144 IsElem e a = IsElem' e a
112
145
113−type family IsLink'' l :: Constraint where
146+-- | A safe link datatype.
114− IsLink'' (e :> Get x) = IsLink' e
147+-- The only way of constructing a 'Link' is using 'safeLink', which means any
115− IsLink'' (e :> Post x) = IsLink' e
148+-- 'Link' is guaranteed to be part of the mentioned API.
116− IsLink'' (e :> Put x) = IsLink' e
149+data Link = Link
117− IsLink'' (e :> Delete) = IsLink' e
150+ { _segments :: [String] -- ^ Segments of "foo/bar" would be ["foo", "bar"]
118− IsLink'' (e :> Raw) = IsLink' e
151+ , _queryParams :: [Param Query]
152+ } deriving Show
153+
119
154
120−type family IsLink' e :: Constraint where
155+-- Phantom types for Param
121− IsLink' (f :: Symbol) = ()
156+data Matrix
157+data Query
122
158
123−type family IsLink e :: Constraint where
159+-- | Query/Matrix param
124− IsLink (a :> b) = Or (And (IsLink' a) (IsLink'' b))
160+data Param a
125− (IsLink'' (a :> b))
161+ = SingleParam String Text
162+ | ArrayElemParam String Text
163+ | FlagParam String
164+ deriving Show
126
165
166+addSegment :: String -> Link -> Link
167+addSegment seg l = l { _segments = _segments l ++ [seg] }
127
168
128−-- | The 'ValidLinkIn f s' constraint holds when 's' is an API that
169+addQueryParam :: Param Query -> Link -> Link
129−-- contains 'f', and 'f' is a link.
170+addQueryParam qp l =
130−class ValidLinkIn f s where
171+ l { _queryParams = _queryParams l ++ [qp] }
131− mkLink :: f -> s -> Link -- ^ This function will only typecheck if `f`
132− -- is an URI within `s`
133
172
134−instance ( IsElem f s
173+-- Not particularly efficient for many updates. Something to optimise if it's
135− , IsLink f
174+-- a problem.
136− , VLinkHelper f) => ValidLinkIn f s where
175+addMatrixParam :: Param Matrix -> Link -> Link
137− mkLink _ _ = Link (vlh (Proxy :: Proxy f))
176+addMatrixParam param l = l { _segments = f (_segments l) }
177+ where
178+ f [] = []
179+ f xs = init xs ++ [g (last xs)]
180+ -- Modify the segment at the "top" of the stack
181+ g :: String -> String
182+ g seg =
183+ case param of
184+ SingleParam k v -> seg <> ";" <> k <> "=" <> escape (unpack v)
185+ ArrayElemParam k v -> seg <> ";" <> k <> "[]=" <> escape (unpack v)
186+ FlagParam k -> seg <> ";" <> k
138
187
139−-- | A safe link datatype.
188+instance Monoid Link where
140−-- The only way of constructing a 'Link' is using 'mkLink', which means any
189+ mempty = Link mempty mempty
141−-- 'Link' is guaranteed to be part of the mentioned API.
190+ mappend (Link a1 b1) (Link a2 b2) =
142−newtype Link = Link { unLink :: String } deriving Show
191+ Link (a1 <> a2) (b1 <> b2)
192+
193+linkURI :: Link -> URI
194+linkURI (Link segments q_params) =
195+ URI mempty -- No scheme (relative)
196+ Nothing -- Or authority (relative)
197+ (intercalate "/" segments)
198+ (makeQueries q_params) mempty
199+ where
200+ makeQueries :: [Param Query] -> String
201+ makeQueries [] = ""
202+ makeQueries xs =
203+ "?" <> intercalate "&" (fmap makeQuery xs)
204+
205+ makeQuery :: Param Query -> String
206+ makeQuery (ArrayElemParam k v) = escape k <> "[]=" <> escape (unpack v)
207+ makeQuery (SingleParam k v) = escape k <> "=" <> escape (unpack v)
208+ makeQuery (FlagParam k) = escape k
209+
210+escape :: String -> String
211+escape = escapeURIString isUnreserved
212+
213+-- | Create a valid (by construction) relative URI with query params.
214+--
215+-- This function will only typecheck if `endpoint` is part of the API `api`
216+safeLink
217+ :: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
218+ => Proxy endpoint -- ^ The API endpoint you would like to point to
219+ -> Proxy api -- ^ The whole API that you this endpoint is a part of
220+ -> MkLink endpoint
221+safeLink endpoint _ = link endpoint mempty
222+
223+-- | Construct a link for an endpoint
224+class HasLink endpoint where
225+ type MkLink endpoint
226+ link :: Proxy endpoint -- ^ The API endpoint you would like to point to
227+ -> Link
228+ -> MkLink endpoint
229+
230+-- Naked symbol instance
231+instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
232+ type MkLink (sym :> sub) = MkLink sub
233+ link _ =
234+ link (Proxy :: Proxy sub) . addSegment seg
235+ where
236+ seg = symbolVal (Proxy :: Proxy sym)
237+
238+
239+-- QueryParam instances
240+instance (KnownSymbol sym, ToText v, HasLink sub)
241+ => HasLink (QueryParam sym v :> sub) where
242+ type MkLink (QueryParam sym v :> sub) = v -> MkLink sub
243+ link _ l v =
244+ link (Proxy :: Proxy sub)
245+ (addQueryParam (SingleParam k (toText v)) l)
246+ where
247+ k :: String
248+ k = symbolVal (Proxy :: Proxy sym)
249+
250+instance (KnownSymbol sym, ToText v, HasLink sub)
251+ => HasLink (QueryParams sym v :> sub) where
252+ type MkLink (QueryParams sym v :> sub) = [v] -> MkLink sub
253+ link _ l =
254+ link (Proxy :: Proxy sub) .
255+ foldl' (\l' v -> addQueryParam (ArrayElemParam k (toText v)) l') l
256+ where
257+ k = symbolVal (Proxy :: Proxy sym)
258+
259+instance (KnownSymbol sym, HasLink sub)
260+ => HasLink (QueryFlag sym :> sub) where
261+ type MkLink (QueryFlag sym :> sub) = Bool -> MkLink sub
262+ link _ l False =
263+ link (Proxy :: Proxy sub) l
264+ link _ l True =
265+ link (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
266+ where
267+ k = symbolVal (Proxy :: Proxy sym)
268+
269+-- MatrixParam instances
270+instance (KnownSymbol sym, ToText v, HasLink sub)
271+ => HasLink (MatrixParam sym v :> sub) where
272+ type MkLink (MatrixParam sym v :> sub) = v -> MkLink sub
273+ link _ l v =
274+ link (Proxy :: Proxy sub) $
275+ addMatrixParam (SingleParam k (toText v)) l
276+ where
277+ k = symbolVal (Proxy :: Proxy sym)
278+
279+instance (KnownSymbol sym, ToText v, HasLink sub)
280+ => HasLink (MatrixParams sym v :> sub) where
281+ type MkLink (MatrixParams sym v :> sub) = [v] -> MkLink sub
282+ link _ l =
283+ link (Proxy :: Proxy sub) .
284+ foldl' (\l' v -> addMatrixParam (ArrayElemParam k (toText v)) l') l
285+ where
286+ k = symbolVal (Proxy :: Proxy sym)
287+
288+instance (KnownSymbol sym, HasLink sub)
289+ => HasLink (MatrixFlag sym :> sub) where
290+ type MkLink (MatrixFlag sym :> sub) = Bool -> MkLink sub
291+ link _ l False =
292+ link (Proxy :: Proxy sub) l
293+ link _ l True =
294+ link (Proxy :: Proxy sub) $ addMatrixParam (FlagParam k) l
295+ where
296+ k = symbolVal (Proxy :: Proxy sym)
297+
298+-- Misc instances
299+instance HasLink sub => HasLink (ReqBody a :> sub) where
300+ type MkLink (ReqBody a :> sub) = MkLink sub
301+ link _ = link (Proxy :: Proxy sub)
302+
303+instance (ToText v, HasLink sub)
304+ => HasLink (Capture sym v :> sub) where
305+ type MkLink (Capture sym v :> sub) = v -> MkLink sub
306+ link _ l v =
307+ link (Proxy :: Proxy sub) $
308+ addSegment (escape . unpack $ toText v) l
309+
310+-- Verb (terminal) instances
311+instance HasLink (Get r) where
312+ type MkLink (Get r) = URI
313+ link _ = linkURI
143
314
144−class VLinkHelper f where
315+instance HasLink (Post r) where
145− vlh :: forall proxy. proxy f -> String
316+ type MkLink (Post r) = URI
317+ link _ = linkURI
146
318
147−instance (KnownSymbol s, VLinkHelper e) => VLinkHelper (s :> e) where
319+instance HasLink (Put r) where
148− vlh _ = "/" ++ symbolVal (Proxy :: Proxy s) ++ vlh (Proxy :: Proxy e)
320+ type MkLink (Put r) = URI
321+ link _ = linkURI
149
322
150−instance VLinkHelper (Get x) where
323+instance HasLink Delete where
151− vlh _ = ""
324+ type MkLink Delete = URI
325+ link _ = linkURI
152
326
153−instance VLinkHelper (Post x) where
327+instance HasLink Raw where
154− vlh _ = ""
328+ type MkLink Raw = URI
329+ link _ = linkURI
test-docs.shnew file+3
@@ -0,0 +1,3 @@
1+#!/bin/sh
2+
3+doctest -isrc -optP-include -optPdist/build/autogen/cabal_macros.h $(find src/ -name '*.hs')
test/Servant/Utils/LinksSpec.hs+61−42
@@ -1,59 +1,78 @@
1{-# LANGUAGE DataKinds #-}
1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE PolyKinds #-}
2{-# LANGUAGE PolyKinds #-}
3{-# LANGUAGE TypeOperators #-}
3{-# LANGUAGE TypeOperators #-}
4+
4module Servant.Utils.LinksSpec where
5module Servant.Utils.LinksSpec where
5
6
6−import Test.Hspec ( Spec, it, describe )
7+import Test.Hspec ( Spec, it, describe, shouldBe, Expectation )
8+import Data.Proxy ( Proxy(..) )
7
9
8import Servant.API
10import Servant.API
9− ( type (:<|>), ReqBody, QueryParam, MatrixParam, MatrixParams, MatrixFlag, Get, Post, Capture, type (:>) )
10−import Servant.QQSpec ( (~>) )
11−import Servant.Utils.Links ( IsElem, IsLink )
12−
13
11
14type TestApi =
12type TestApi =
13+ -- Capture and query/matrix params
15− "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Get Bool
14+ "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Delete
16− :<|> "greet" :> ReqBody 'True :> Post Bool
15+
17− :<|> "parent" :> MatrixParams "name" String :> "child" :> MatrixParam "gender" String :> Get String
16+ :<|> "parent" :> MatrixParams "name" String :> "child"
17+ :> MatrixParam "gender" String :> Get String
18+
19+ -- Flags
20+ :<|> "ducks" :> MatrixFlag "yellow" :> MatrixFlag "loud" :> Delete
21+ :<|> "balls" :> QueryFlag "bouncy" :> QueryFlag "fast" :> Delete
22+
23+ -- All of the verbs
24+ :<|> "get" :> Get ()
25+ :<|> "put" :> Put ()
26+ :<|> "post" :> ReqBody 'True :> Post ()
27+ :<|> "delete" :> Header "ponies" :> Delete
28+ :<|> "raw" :> Raw
18
29
19type TestLink = "hello" :> "hi" :> Get Bool
30type TestLink = "hello" :> "hi" :> Get Bool
20type TestLink2 = "greet" :> Post Bool
31type TestLink2 = "greet" :> Post Bool
21type TestLink3 = "parent" :> "child" :> Get String
32type TestLink3 = "parent" :> "child" :> Get String
22
33
23−type BadTestLink = "hallo" :> "hi" :> Get Bool
34+api :: Proxy TestApi
24−type BadTestLink2 = "greet" :> Get Bool
35+api = Proxy
25−type BadTestLink3 = "parent" :> "child" :> MatrixFlag "male" :> Get String
26−
27−type NotALink = "hello" :> Capture "x" Bool :> Get Bool
28−type NotALink2 = "hello" :> ReqBody 'True :> Get Bool
29
36
30−data Proxy x = Proxy
37+-- | Convert a link to a URI and ensure that this maps to the given string
31−class ReflectT (x::Bool) where { reflected :: Proxy x -> Bool }
38+-- given string
32−instance ReflectT 'True where { reflected _ = True }
39+shouldBeURI :: URI -> String -> Expectation
33−instance ReflectT 'False where { reflected _ = False }
40+shouldBeURI link expected =
41+ show link `shouldBe` expected
34
42
35spec :: Spec
43spec :: Spec
36−spec = describe "Servant.API.Elem" $ do
44+spec = describe "Servant.Utils.Links" $ do
37− isElem
45+ it "Generates correct links for capture query and matrix params" $ do
38− isLink
46+ let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete)
39−
47+ safeLink l1 api "hi" `shouldBeURI` "hello/hi"
40−isElem :: Spec
48+
41−isElem = describe "IsElem" $ do
49+ let l2 = Proxy :: Proxy ("hello" :> Capture "name" String
42− it "is True when the first argument is an url within the second" $ do
50+ :> QueryParam "capital" Bool
43− reflected (Proxy::Proxy (IsElem TestLink TestApi)) ~> True
51+ :> Delete)
44− reflected (Proxy::Proxy (IsElem TestLink2 TestApi)) ~> True
52+ safeLink l2 api "bye" True `shouldBeURI` "hello/bye?capital=true"
45− reflected (Proxy::Proxy (IsElem TestLink3 TestApi)) ~> True
53+
46− it "is False when the first argument is not an url within the second" $ do
54+ let l3 = Proxy :: Proxy ("parent" :> MatrixParams "name" String
47− reflected (Proxy::Proxy (IsElem BadTestLink TestApi)) ~> False
55+ :> "child"
48− reflected (Proxy::Proxy (IsElem BadTestLink2 TestApi)) ~> False
56+ :> MatrixParam "gender" String
49− reflected (Proxy::Proxy (IsElem BadTestLink3 TestApi)) ~> False
57+ :> Get String)
50−
58+ safeLink l3 api ["Hubert?x=;&", "Cumberdale"] "Edward?"
51−isLink :: Spec
59+ `shouldBeURI` "parent;name[]=Hubert%3Fx%3D%3B%26;\
52−isLink = describe "IsLink" $ do
60+ \name[]=Cumberdale/child;gender=Edward%3F"
53− it "is True when all Subs are paths and the last is a method" $ do
61+
54− reflected (Proxy::Proxy (IsLink TestLink)) ~> True
62+ it "Generates correct links for query and matrix flags" $ do
55− reflected (Proxy::Proxy (IsLink TestLink2)) ~> True
63+ let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy"
56− reflected (Proxy::Proxy (IsLink TestLink3)) ~> True
64+ :> QueryFlag "fast" :> Delete)
57− it "is False of anything with captures" $ do
65+ safeLink l1 api True True `shouldBeURI` "balls?bouncy&fast"
58− reflected (Proxy::Proxy (IsLink NotALink)) ~> False
66+ safeLink l1 api False True `shouldBeURI` "balls?fast"
59− reflected (Proxy::Proxy (IsLink NotALink2)) ~> False
67+
68+ let l2 = Proxy :: Proxy ("ducks" :> MatrixFlag "yellow"
69+ :> MatrixFlag "loud" :> Delete)
70+ safeLink l2 api True True `shouldBeURI` "ducks;yellow;loud"
71+ safeLink l2 api False True `shouldBeURI` "ducks;loud"
72+
73+ it "Generates correct links for all of the verbs" $ do
74+ safeLink (Proxy :: Proxy ("get" :> Get ())) api `shouldBeURI` "get"
75+ safeLink (Proxy :: Proxy ("put" :> Put ())) api `shouldBeURI` "put"
76+ safeLink (Proxy :: Proxy ("post" :> Post ())) api `shouldBeURI` "post"
77+ safeLink (Proxy :: Proxy ("delete" :> Delete)) api `shouldBeURI` "delete"
78+ safeLink (Proxy :: Proxy ("raw" :> Raw)) api `shouldBeURI` "raw"