on master help sign in
Flip safeLink and rename link to toLink
commitd03788ee21c6294fc5a9947ec5c7085fb21c76a9 authorChristian Marie <christian@ponies.io> dateFri 30 Jan 2015, 11:03 (12 years ago) parent12cf4e0
3 files changed+72−59
src/Servant/API.hs+1−1 src/Servant/Utils/Links.hs+55−44 test/Servant/Utils/LinksSpec.hs+16−14
src/Servant/API.hs+1−1
@@ -52,4 +52,4 @@ import Servant.API.Raw ( Raw )
5252import Servant.API.ReqBody ( ReqBody )
5353import Servant.API.Sub ( (:>)(..) )
5454import Servant.QQ ( sitemap )
55−import Servant.Utils.Links ( safeLink, URI(..) )
55+import Servant.Utils.Links ( safeLink, URI(..), IsElem, IsElem', HasLink(..) )
src/Servant/Utils/Links.hs+55−44
@@ -25,12 +25,12 @@
2525-- >>> let api = Proxy :: Proxy API
2626--
2727-- It is possible to generate links that are guaranteed to be within 'API' with
28−-- 'safeLink'. The first argument to 'safeLink' is a type representing the
29−-- endpoint you would like to point to. This will need to end in a verb like
30−-- Get, or Post. The second argument is the API in which you would like to
31−-- ensure the endpoint is within. Further arguments be required depending on
32−-- the type of the endpoint. If everything lines up you will get a 'URI' out
33−-- the other end.
28+-- 'safeLink'. The first argument to 'safeLink' is a type representing the API
29+-- you would like to restrict links to. The second argument is the destination
30+-- endpoint you would like the link to point to, this will need to end with a
31+-- verb like GET or POST. Further arguments may be required depending on the
32+-- type of the endpoint. If everything lines up you will get a 'URI' out the
33+-- other end.
3434--
3535-- You may omit 'QueryParam's and the like should you not want to provide them,
3636-- but types which form part of the URL path like 'Capture' must be included.
@@ -40,35 +40,46 @@
4040-- with an example. Here, a link is generated with no parameters:
4141--
4242-- >>> let hello = Proxy :: Proxy ("hello" :> Get Int)
43−-- >>> print (safeLink hello api :: URI)
43+-- >>> print (safeLink api hello :: URI)
4444-- hello
4545--
4646-- If the API has an endpoint with parameters then we can generate links with
4747-- or without those:
4848--
4949-- >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete)
50−-- >>> print $ safeLink with api "Hubert"
50+-- >>> print $ safeLink api with "Hubert"
5151-- bye?name=Hubert
5252--
5353-- >>> let without = Proxy :: Proxy ("bye" :> Delete)
54−-- >>> print $ safeLink without api
54+-- >>> print $ safeLink api without
5555-- bye
5656--
57+-- If you would like create a helper for generating links only within that API,
58+-- you can partially apply safeLink if you specify a correct type signature
59+-- like so:
60+--
61+-- >>> :set -XConstraintKinds
62+-- >>> :{
63+-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
64+-- >>> => Proxy endpoint -> MkLink endpoint
65+-- >>> apiLink = safeLink api
66+-- >>> :}
67+--
5768-- Attempting to construct a link to an endpoint that does not exist in api
5869-- will result in a type error like this:
5970--
6071-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete)
61−-- >>> safeLink bad_link api
72+-- >>> safeLink api bad_link
6273-- <BLANKLINE>
63−-- <interactive>:56:1:
74+-- <interactive>:64:1:
6475-- Could not deduce (Or
6576-- (IsElem' Delete (Get Int))
6677-- (IsElem'
6778-- ("hello" :> Delete)
6879-- ("bye" :> (QueryParam "name" String :> Delete))))
6980-- arising from a use of ‘safeLink’
70−-- In the expression: safeLink bad_link api
71−-- In an equation for ‘it’: it = safeLink bad_link api
81+-- In the expression: safeLink api bad_link
82+-- In an equation for ‘it’: it = safeLink api bad_link
7283--
7384-- This error is essentially saying that the type family couldn't find
7485-- bad_link under api after trying the open (but empty) type family
@@ -213,23 +224,23 @@ escape = escapeURIString isUnreserved
213224-- This function will only typecheck if `endpoint` is part of the API `api`
214225safeLink
215226 :: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
216− => Proxy endpoint -- ^ The API endpoint you would like to point to
217− -> Proxy api -- ^ The whole API that this endpoint is a part of
227+ => Proxy api -- ^ The whole API that this endpoint is a part of
228+ -> Proxy endpoint -- ^ The API endpoint you would like to point to
218229 -> MkLink endpoint
219−safeLink endpoint _ = link endpoint (Link mempty mempty)
230+safeLink _ endpoint = toLink endpoint (Link mempty mempty)
220231
221−-- | Construct a link for an endpoint.
232+-- | Construct a toLink for an endpoint.
222233class HasLink endpoint where
223234 type MkLink endpoint
224− link :: Proxy endpoint -- ^ The API endpoint you would like to point to
235+ toLink :: Proxy endpoint -- ^ The API endpoint you would like to point to
225236 -> Link
226237 -> MkLink endpoint
227238
228239-- Naked symbol instance
229240instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
230241 type MkLink (sym :> sub) = MkLink sub
231− link _ =
232− link (Proxy :: Proxy sub) . addSegment seg
242+ toLink _ =
243+ toLink (Proxy :: Proxy sub) . addSegment seg
233244 where
234245 seg = symbolVal (Proxy :: Proxy sym)
235246
@@ -238,8 +249,8 @@ instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
238249instance (KnownSymbol sym, ToText v, HasLink sub)
239250 => HasLink (QueryParam sym v :> sub) where
240251 type MkLink (QueryParam sym v :> sub) = v -> MkLink sub
241− link _ l v =
242− link (Proxy :: Proxy sub)
252+ toLink _ l v =
253+ toLink (Proxy :: Proxy sub)
243254 (addQueryParam (SingleParam k (toText v)) l)
244255 where
245256 k :: String
@@ -248,8 +259,8 @@ instance (KnownSymbol sym, ToText v, HasLink sub)
248259instance (KnownSymbol sym, ToText v, HasLink sub)
249260 => HasLink (QueryParams sym v :> sub) where
250261 type MkLink (QueryParams sym v :> sub) = [v] -> MkLink sub
251− link _ l =
252− link (Proxy :: Proxy sub) .
262+ toLink _ l =
263+ toLink (Proxy :: Proxy sub) .
253264 foldl' (\l' v -> addQueryParam (ArrayElemParam k (toText v)) l') l
254265 where
255266 k = symbolVal (Proxy :: Proxy sym)
@@ -257,10 +268,10 @@ instance (KnownSymbol sym, ToText v, HasLink sub)
257268instance (KnownSymbol sym, HasLink sub)
258269 => HasLink (QueryFlag sym :> sub) where
259270 type MkLink (QueryFlag sym :> sub) = Bool -> MkLink sub
260− link _ l False =
261− link (Proxy :: Proxy sub) l
262− link _ l True =
263− link (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
271+ toLink _ l False =
272+ toLink (Proxy :: Proxy sub) l
273+ toLink _ l True =
274+ toLink (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
264275 where
265276 k = symbolVal (Proxy :: Proxy sym)
266277
@@ -268,8 +279,8 @@ instance (KnownSymbol sym, HasLink sub)
268279instance (KnownSymbol sym, ToText v, HasLink sub)
269280 => HasLink (MatrixParam sym v :> sub) where
270281 type MkLink (MatrixParam sym v :> sub) = v -> MkLink sub
271− link _ l v =
272− link (Proxy :: Proxy sub) $
282+ toLink _ l v =
283+ toLink (Proxy :: Proxy sub) $
273284 addMatrixParam (SingleParam k (toText v)) l
274285 where
275286 k = symbolVal (Proxy :: Proxy sym)
@@ -277,8 +288,8 @@ instance (KnownSymbol sym, ToText v, HasLink sub)
277288instance (KnownSymbol sym, ToText v, HasLink sub)
278289 => HasLink (MatrixParams sym v :> sub) where
279290 type MkLink (MatrixParams sym v :> sub) = [v] -> MkLink sub
280− link _ l =
281− link (Proxy :: Proxy sub) .
291+ toLink _ l =
292+ toLink (Proxy :: Proxy sub) .
282293 foldl' (\l' v -> addMatrixParam (ArrayElemParam k (toText v)) l') l
283294 where
284295 k = symbolVal (Proxy :: Proxy sym)
@@ -286,42 +297,42 @@ instance (KnownSymbol sym, ToText v, HasLink sub)
286297instance (KnownSymbol sym, HasLink sub)
287298 => HasLink (MatrixFlag sym :> sub) where
288299 type MkLink (MatrixFlag sym :> sub) = Bool -> MkLink sub
289− link _ l False =
290− link (Proxy :: Proxy sub) l
291− link _ l True =
292− link (Proxy :: Proxy sub) $ addMatrixParam (FlagParam k) l
300+ toLink _ l False =
301+ toLink (Proxy :: Proxy sub) l
302+ toLink _ l True =
303+ toLink (Proxy :: Proxy sub) $ addMatrixParam (FlagParam k) l
293304 where
294305 k = symbolVal (Proxy :: Proxy sym)
295306
296307-- Misc instances
297308instance HasLink sub => HasLink (ReqBody a :> sub) where
298309 type MkLink (ReqBody a :> sub) = MkLink sub
299− link _ = link (Proxy :: Proxy sub)
310+ toLink _ = toLink (Proxy :: Proxy sub)
300311
301312instance (ToText v, HasLink sub)
302313 => HasLink (Capture sym v :> sub) where
303314 type MkLink (Capture sym v :> sub) = v -> MkLink sub
304− link _ l v =
305− link (Proxy :: Proxy sub) $
315+ toLink _ l v =
316+ toLink (Proxy :: Proxy sub) $
306317 addSegment (escape . unpack $ toText v) l
307318
308319-- Verb (terminal) instances
309320instance HasLink (Get r) where
310321 type MkLink (Get r) = URI
311− link _ = linkURI
322+ toLink _ = linkURI
312323
313324instance HasLink (Post r) where
314325 type MkLink (Post r) = URI
315− link _ = linkURI
326+ toLink _ = linkURI
316327
317328instance HasLink (Put r) where
318329 type MkLink (Put r) = URI
319− link _ = linkURI
330+ toLink _ = linkURI
320331
321332instance HasLink Delete where
322333 type MkLink Delete = URI
323− link _ = linkURI
334+ toLink _ = linkURI
324335
325336instance HasLink Raw where
326337 type MkLink Raw = URI
327− link _ = linkURI
338+ toLink _ = linkURI
test/Servant/Utils/LinksSpec.hs+16−14
@@ -1,6 +1,7 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE PolyKinds #-}
33{-# LANGUAGE TypeOperators #-}
4+{-# LANGUAGE ConstraintKinds #-}
45
56module Servant.Utils.LinksSpec where
67
@@ -31,8 +32,9 @@ type TestLink = "hello" :> "hi" :> Get Bool
3132type TestLink2 = "greet" :> Post Bool
3233type TestLink3 = "parent" :> "child" :> Get String
3334
34−api :: Proxy TestApi
35−api = Proxy
35+apiLink :: (IsElem endpoint TestApi, HasLink endpoint)
36+ => Proxy endpoint -> MkLink endpoint
37+apiLink = safeLink (Proxy :: Proxy TestApi)
3638
3739-- | Convert a link to a URI and ensure that this maps to the given string
3840-- given string
@@ -44,35 +46,35 @@ spec :: Spec
4446spec = describe "Servant.Utils.Links" $ do
4547 it "Generates correct links for capture query and matrix params" $ do
4648 let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete)
47− safeLink l1 api "hi" `shouldBeURI` "hello/hi"
49+ apiLink l1 "hi" `shouldBeURI` "hello/hi"
4850
4951 let l2 = Proxy :: Proxy ("hello" :> Capture "name" String
5052 :> QueryParam "capital" Bool
5153 :> Delete)
52− safeLink l2 api "bye" True `shouldBeURI` "hello/bye?capital=true"
54+ apiLink l2 "bye" True `shouldBeURI` "hello/bye?capital=true"
5355
5456 let l3 = Proxy :: Proxy ("parent" :> MatrixParams "name" String
5557 :> "child"
5658 :> MatrixParam "gender" String
5759 :> Get String)
58− safeLink l3 api ["Hubert?x=;&", "Cumberdale"] "Edward?"
60+ apiLink l3 ["Hubert?x=;&", "Cumberdale"] "Edward?"
5961 `shouldBeURI` "parent;name[]=Hubert%3Fx%3D%3B%26;\
6062 \name[]=Cumberdale/child;gender=Edward%3F"
6163
6264 it "Generates correct links for query and matrix flags" $ do
6365 let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy"
6466 :> QueryFlag "fast" :> Delete)
65− safeLink l1 api True True `shouldBeURI` "balls?bouncy&fast"
66− safeLink l1 api False True `shouldBeURI` "balls?fast"
67+ apiLink l1 True True `shouldBeURI` "balls?bouncy&fast"
68+ apiLink l1 False True `shouldBeURI` "balls?fast"
6769
6870 let l2 = Proxy :: Proxy ("ducks" :> MatrixFlag "yellow"
6971 :> MatrixFlag "loud" :> Delete)
70− safeLink l2 api True True `shouldBeURI` "ducks;yellow;loud"
71− safeLink l2 api False True `shouldBeURI` "ducks;loud"
72+ apiLink l2 True True `shouldBeURI` "ducks;yellow;loud"
73+ apiLink l2 False True `shouldBeURI` "ducks;loud"
7274
7375 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"
76+ apiLink (Proxy :: Proxy ("get" :> Get ())) `shouldBeURI` "get"
77+ apiLink (Proxy :: Proxy ("put" :> Put ())) `shouldBeURI` "put"
78+ apiLink (Proxy :: Proxy ("post" :> Post ())) `shouldBeURI` "post"
79+ apiLink (Proxy :: Proxy ("delete" :> Delete)) `shouldBeURI` "delete"
80+ apiLink (Proxy :: Proxy ("raw" :> Raw)) `shouldBeURI` "raw"