on master help sign in
QQ and safe links documentation.
63584d5Julian K. Arni
src/Servant/Utils/ApiQuasiQuoting.hs+43
@@ -5,6 +5,27 @@
5{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6{-# LANGUAGE TemplateHaskell #-}
6{-# LANGUAGE TemplateHaskell #-}
7{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
7{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
8+-- | QuasiQuoting utilities for API types.
9+--
10+-- 'sitemap' allows you to write your type in a very natural way:
11+--
12+-- @
13+-- [sitemap|
14+-- PUT hello String -> ()
15+-- POST hello/p:Int String -> ()
16+-- GET hello/?name:String Int
17+-- |]
18+-- @
19+--
20+-- Will generate:
21+--
22+-- @
23+-- "hello" :> ReqBody String :> Put ()
24+-- :<|> "hello" :> Capture "p" Int :> ReqBody String :> Post ()
25+-- :<|> "hello" :> QueryParam "name" String :> Get Int
26+-- @
27+--
28+-- Note the '/' before a 'QueryParam'!
8module Servant.Utils.ApiQuasiQuoting where
29module Servant.Utils.ApiQuasiQuoting where
9
30
10import Control.Monad (void)
31import Control.Monad (void)
@@ -23,6 +44,11 @@ import Servant.API.ReqBody
23import Servant.API.Sub
44import Servant.API.Sub
24import Servant.API.Alternative
45import Servant.API.Alternative
25
46
47+-- | Finally-tagless encoding for our DSL.
48+-- Keeping 'repr'' and 'repr' distinct when writing functions with an
49+-- @ExpSYM@ context ensures certain invariants (for instance, that there is
50+-- only one of 'get', 'post', 'put', and 'delete' in a value), but
51+-- sometimes requires a little more work.
26class ExpSYM repr' repr | repr -> repr', repr' -> repr where
52class ExpSYM repr' repr | repr -> repr', repr' -> repr where
27 lit :: String -> repr' -> repr
53 lit :: String -> repr' -> repr
28 capture :: String -> String -> repr -> repr
54 capture :: String -> String -> repr -> repr
@@ -148,6 +174,23 @@ parseAll = do
148 where union :: Type -> Type -> Type
174 where union :: Type -> Type -> Type
149 union a = AppT (AppT (ConT ''(:<|>)) a)
175 union a = AppT (AppT (ConT ''(:<|>)) a)
150
176
177+-- | The sitemap QuasiQuoter.
178+--
179+-- * @.../<var>:<type>/...@ becomes a capture
180+--
181+-- * @.../?<var>:<type>@ becomes a query parameter
182+--
183+-- * @<method> ... <typ>@ becomes a method returning @<typ>@
184+--
185+-- * @<method> ... <typ1> -> <typ2>@ becomes a method with request
186+-- body of @<typ1>@ and returning @<typ2>@
187+--
188+-- Comments are allowed, and have the standard Haskell format
189+--
190+-- * @--@ for inline
191+--
192+-- * @{- ... -}@ for block
193+--
151sitemap :: QuasiQuoter
194sitemap :: QuasiQuoter
152sitemap = QuasiQuoter { quoteExp = undefined
195sitemap = QuasiQuoter { quoteExp = undefined
153 , quotePat = undefined
196 , quotePat = undefined
src/Servant/Utils/Links.hs+26
@@ -6,6 +6,32 @@
6{-# LANGUAGE FunctionalDependencies #-}
6{-# LANGUAGE FunctionalDependencies #-}
7{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE UndecidableInstances #-}
8{-# LANGUAGE UndecidableInstances #-}
9+-- | Type safe internal links.
10+--
11+-- Provides the function 'mkLink':
12+-- @
13+-- type API = Proxy ("hello" :> Get Int
14+-- :<|> "bye" :> QueryParam "name" String :> Post Bool)
15+--
16+-- api :: API
17+-- api = proxy
18+--
19+-- link1 :: Proxy ("hello" :> Get Int)
20+-- link1 = proxy
21+--
22+-- link2 :: Proxy ("hello" :> Delete)
23+-- link2 = proxy
24+--
25+-- mkLink link1 API -- typechecks, returns 'Link "/hello"'
26+--
27+-- mkLink link2 API -- doesn't typecheck
28+-- @
29+--
30+-- That is, 'mkLink' takes two arguments, a link proxy and a sitemap, and
31+-- returns a 'Link', but only typechecks if the link proxy is a valid link,
32+-- and part of the sitemap.
33+--
34+-- __N.B.:__ 'mkLink' assumes a capture matches any string (without slashes).
9module Servant.Utils.Links where
35module Servant.Utils.Links where
10
36
11import Data.Proxy
37import Data.Proxy