git.iko.soy/typeable/servant
on master help sign in
Export open type family IsElem' to allow custom API types in Links.
7336ecbChristian Marie
src/Servant/Utils/Links.hs+40−15
@@ -6,29 +6,49 @@
6{-# LANGUAGE FunctionalDependencies #-}
6{-# LANGUAGE FunctionalDependencies #-}
7{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE UndecidableInstances #-}
8{-# LANGUAGE UndecidableInstances #-}
9+
9-- | Type safe internal links.
10-- | Type safe internal links.
10--
11--
11-- Provides the function 'mkLink':
12-- Provides the function 'mkLink':
12--
13--
13-- @
14-- @
15+-- {-# LANGUAGE DataKinds #-}
16+-- {-# LANGUAGE TypeFamilies #-}
17+-- {-# LANGUAGE TypeOperators #-}
18+--
19+-- import Servant.API
20+-- import Servant.Utils.Links
21+--
22+-- -- You might want to put some custom types in your API
23+-- data Thing = Thing
24+--
25+-- -- If you want these to form part of a valid URL, just add them to the
26+-- -- open type family IsElem' like so:
27+-- type instance IsElem' e (Thing :> s) = IsElem e s
28+--
14−-- type API = Proxy ("hello" :> Get Int
29+-- type API = "hello" :> Thing :> Get Int
15−-- :<|> "bye" :> QueryParam "name" String :> Post Bool)
30+-- :<|> "bye" :> QueryParam "name" String :> Post Bool
16--
31--
17-- api :: API
32-- api :: API
18−-- api = proxy
33+-- api = undefined
19--
34--
20−-- link1 :: Proxy ("hello" :> Get Int)
35+-- link1 :: "hello" :> Get Int
21−-- link1 = proxy
36+-- link1 = undefined
22--
37--
23−-- link2 :: Proxy ("hello" :> Delete)
38+-- link2 :: "hello" :> Delete
24−-- link2 = proxy
39+-- link2 = undefined
25--
40--
41+-- main :: IO ()
42+-- main =
26−-- mkLink link1 API -- typechecks, returns 'Link "/hello"'
43+-- -- typechecks, prints "/hello"'
44+-- let Link str = mkLink link1 api
45+-- in putStrLn str
27--
46--
28−-- mkLink link2 API -- doesn't typecheck
47+-- -- doesn't typecheck
48+-- -- mkLink link2 api
29-- @
49-- @
30--
50--
31−-- That is, 'mkLink' takes two arguments, a link proxy and a sitemap, and
51+-- That is, 'mkLink' takes two arguments, a link and a sitemap, and
32-- returns a 'Link', but only typechecks if the link proxy is a valid link,
52-- returns a 'Link', but only typechecks if the link proxy is a valid link,
33-- and part of the sitemap.
53-- and part of the sitemap.
34--
54--
@@ -37,7 +57,7 @@ module Servant.Utils.Links (
37 -- * Link and mkLink
57 -- * Link and mkLink
38 -- | The only end-user utilities
58 -- | The only end-user utilities
39 mkLink
59 mkLink
40− , Link
60+ , Link, unLink
41 -- * Internal
61 -- * Internal
42 -- | These functions will likely only be of interest if you are writing
62 -- | These functions will likely only be of interest if you are writing
43 -- more API combinators and would like to extend the behavior of
63 -- more API combinators and would like to extend the behavior of
@@ -45,8 +65,9 @@ module Servant.Utils.Links (
45 , ValidLinkIn()
65 , ValidLinkIn()
46 , VLinkHelper(..)
66 , VLinkHelper(..)
47 , IsElem
67 , IsElem
68+ , IsElem'
48 , IsLink
69 , IsLink
49− )where
70+ ) where
50
71
51import Data.Proxy ( Proxy(..) )
72import Data.Proxy ( Proxy(..) )
52import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
73import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal )
@@ -60,6 +81,7 @@ import Servant.API.Post ( Post )
60import Servant.API.Put ( Put )
81import Servant.API.Put ( Put )
61import Servant.API.Delete ( Delete )
82import Servant.API.Delete ( Delete )
62import Servant.API.Sub ( type (:>) )
83import Servant.API.Sub ( type (:>) )
84+import Servant.API.Raw ( Raw )
63import Servant.API.Alternative ( type (:<|>) )
85import Servant.API.Alternative ( type (:<|>) )
64
86
65
87
@@ -73,6 +95,8 @@ type family And a b where
73 And a 'False = 'False
95 And a 'False = 'False
74 And 'False b = 'False
96 And 'False b = 'False
75
97
98+type family IsElem' a s :: Bool
99+
76type family IsElem a s where
100type family IsElem a s where
77 IsElem e (sa :<|> sb) = Or (IsElem e sa) (IsElem e sb)
101 IsElem e (sa :<|> sb) = Or (IsElem e sa) (IsElem e sb)
78 IsElem (e :> sa) (e :> sb) = IsElem sa sb
102 IsElem (e :> sa) (e :> sb) = IsElem sa sb
@@ -85,13 +109,14 @@ type family IsElem a s where
85 IsElem sa (MatrixParams x y :> sb) = IsElem sa sb
109 IsElem sa (MatrixParams x y :> sb) = IsElem sa sb
86 IsElem sa (MatrixFlag x :> sb) = IsElem sa sb
110 IsElem sa (MatrixFlag x :> sb) = IsElem sa sb
87 IsElem e e = 'True
111 IsElem e e = 'True
88− IsElem e a = 'False
112+ IsElem e a = IsElem' e a
89
113
90type family IsLink'' l where
114type family IsLink'' l where
91 IsLink'' (e :> Get x) = IsLink' e
115 IsLink'' (e :> Get x) = IsLink' e
92 IsLink'' (e :> Post x) = IsLink' e
116 IsLink'' (e :> Post x) = IsLink' e
93 IsLink'' (e :> Put x) = IsLink' e
117 IsLink'' (e :> Put x) = IsLink' e
94 IsLink'' (e :> Delete) = IsLink' e
118 IsLink'' (e :> Delete) = IsLink' e
119+ IsLink'' (e :> Raw) = IsLink' e
95 IsLink'' a = 'False
120 IsLink'' a = 'False
96
121
97type family IsLink' e where
122type family IsLink' e where
@@ -109,14 +134,14 @@ class ValidLinkIn f s where
109 -- is an URI within `s`
134 -- is an URI within `s`
110
135
111instance ( IsElem f s ~ 'True
136instance ( IsElem f s ~ 'True
112− , IsLink f ~ 'True
137+ , IsLink f ~ 'True
113 , VLinkHelper f) => ValidLinkIn f s where
138 , VLinkHelper f) => ValidLinkIn f s where
114 mkLink _ _ = Link (vlh (Proxy :: Proxy f))
139 mkLink _ _ = Link (vlh (Proxy :: Proxy f))
115
140
116-- | A safe link datatype.
141-- | A safe link datatype.
117-- The only way of constructing a 'Link' is using 'mkLink', which means any
142-- The only way of constructing a 'Link' is using 'mkLink', which means any
118-- 'Link' is guaranteed to be part of the mentioned API.
143-- 'Link' is guaranteed to be part of the mentioned API.
119−data Link = Link String deriving Show
144+newtype Link = Link { unLink :: String } deriving Show
120
145
121class VLinkHelper f where
146class VLinkHelper f where
122 vlh :: forall proxy. proxy f -> String
147 vlh :: forall proxy. proxy f -> String