From 63584d52b75a779c4229e84af358e83b1e2931f9 Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Tue, 25 Nov 2014 16:10:59 +0100 Subject: [PATCH 1/3] QQ and safe links documentation. --- src/Servant/Utils/ApiQuasiQuoting.hs | 43 ++++++++++++++++++++++++++++ src/Servant/Utils/Links.hs | 26 +++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/Servant/Utils/ApiQuasiQuoting.hs b/src/Servant/Utils/ApiQuasiQuoting.hs index e768c73e..9337a42d 100644 --- a/src/Servant/Utils/ApiQuasiQuoting.hs +++ b/src/Servant/Utils/ApiQuasiQuoting.hs @@ -5,6 +5,27 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-unused-do-bind #-} +-- | QuasiQuoting utilities for API types. +-- +-- 'sitemap' allows you to write your type in a very natural way: +-- +-- @ +-- [sitemap| +-- PUT hello String -> () +-- POST hello/p:Int String -> () +-- GET hello/?name:String Int +-- |] +-- @ +-- +-- Will generate: +-- +-- @ +-- "hello" :> ReqBody String :> Put () +-- :<|> "hello" :> Capture "p" Int :> ReqBody String :> Post () +-- :<|> "hello" :> QueryParam "name" String :> Get Int +-- @ +-- +-- Note the '/' before a 'QueryParam'! module Servant.Utils.ApiQuasiQuoting where import Control.Monad (void) @@ -23,6 +44,11 @@ import Servant.API.ReqBody import Servant.API.Sub import Servant.API.Alternative +-- | Finally-tagless encoding for our DSL. +-- Keeping 'repr'' and 'repr' distinct when writing functions with an +-- @ExpSYM@ context ensures certain invariants (for instance, that there is +-- only one of 'get', 'post', 'put', and 'delete' in a value), but +-- sometimes requires a little more work. class ExpSYM repr' repr | repr -> repr', repr' -> repr where lit :: String -> repr' -> repr capture :: String -> String -> repr -> repr @@ -148,6 +174,23 @@ parseAll = do where union :: Type -> Type -> Type union a = AppT (AppT (ConT ''(:<|>)) a) +-- | The sitemap QuasiQuoter. +-- +-- * @.../:/...@ becomes a capture +-- +-- * @.../?:@ becomes a query parameter +-- +-- * @ ... @ becomes a method returning @@ +-- +-- * @ ... -> @ becomes a method with request +-- body of @@ and returning @@ +-- +-- Comments are allowed, and have the standard Haskell format +-- +-- * @--@ for inline +-- +-- * @{- ... -}@ for block +-- sitemap :: QuasiQuoter sitemap = QuasiQuoter { quoteExp = undefined , quotePat = undefined diff --git a/src/Servant/Utils/Links.hs b/src/Servant/Utils/Links.hs index 97f65f21..f25533f5 100644 --- a/src/Servant/Utils/Links.hs +++ b/src/Servant/Utils/Links.hs @@ -6,6 +6,32 @@ {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-} +-- | Type safe internal links. +-- +-- Provides the function 'mkLink': +-- @ +-- type API = Proxy ("hello" :> Get Int +-- :<|> "bye" :> QueryParam "name" String :> Post Bool) +-- +-- api :: API +-- api = proxy +-- +-- link1 :: Proxy ("hello" :> Get Int) +-- link1 = proxy +-- +-- link2 :: Proxy ("hello" :> Delete) +-- link2 = proxy +-- +-- mkLink link1 API -- typechecks, returns 'Link "/hello"' +-- +-- mkLink link2 API -- doesn't typecheck +-- @ +-- +-- That is, 'mkLink' takes two arguments, a link proxy and a sitemap, and +-- returns a 'Link', but only typechecks if the link proxy is a valid link, +-- and part of the sitemap. +-- +-- __N.B.:__ 'mkLink' assumes a capture matches any string (without slashes). module Servant.Utils.Links where import Data.Proxy From dee47654f9d1b0fe6c41330752512643a1deb47e Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Tue, 25 Nov 2014 16:35:56 +0100 Subject: [PATCH 2/3] Rename ApiQuasiQuoting. And fix haddocks for it. --- servant.cabal | 2 +- src/Servant.hs | 4 ++-- src/Servant/API.hs | 4 ++-- src/Servant/{Utils/ApiQuasiQuoting.hs => QQ.hs} | 12 ++++-------- src/Servant/Utils/Links.hs | 1 + .../{Utils/ApiQuasiQuotingSpec.hs => QQSpec.hs} | 2 +- 6 files changed, 11 insertions(+), 14 deletions(-) rename src/Servant/{Utils/ApiQuasiQuoting.hs => QQ.hs} (96%) rename test/Servant/{Utils/ApiQuasiQuotingSpec.hs => QQSpec.hs} (99%) diff --git a/servant.cabal b/servant.cabal index 52d039a7..c0048a12 100644 --- a/servant.cabal +++ b/servant.cabal @@ -31,8 +31,8 @@ library Servant.Common.Req Servant.Common.Text Servant.Docs + Servant.QQ Servant.Server - Servant.Utils.ApiQuasiQuoting Servant.Utils.Links Servant.Utils.StaticFiles build-depends: diff --git a/src/Servant.hs b/src/Servant.hs index 69fb50a2..a6263ab7 100644 --- a/src/Servant.hs +++ b/src/Servant.hs @@ -12,7 +12,7 @@ module Servant ( -- | Using your types in request paths and query string parameters module Servant.Common.Text, -- | Utilities on top of the servant core - module Servant.Utils.ApiQuasiQuoting, + module Servant.QQ, module Servant.Utils.Links, module Servant.Utils.StaticFiles, -- | Useful re-exports @@ -25,6 +25,6 @@ import Servant.Client import Servant.Common.Text import Servant.Docs import Servant.Server -import Servant.Utils.ApiQuasiQuoting +import Servant.QQ import Servant.Utils.Links import Servant.Utils.StaticFiles diff --git a/src/Servant/API.hs b/src/Servant/API.hs index 76b4fbcc..c5a7a288 100644 --- a/src/Servant/API.hs +++ b/src/Servant/API.hs @@ -31,7 +31,7 @@ module Servant.API ( -- * Utilities -- | QuasiQuotes for endpoints - module Servant.Utils.ApiQuasiQuoting, + module Servant.QQ, -- | Type-safe internal URLs module Servant.Utils.Links, ) where @@ -46,6 +46,6 @@ import Servant.API.QueryParam import Servant.API.Raw import Servant.API.ReqBody import Servant.API.Sub -import Servant.Utils.ApiQuasiQuoting (sitemap) +import Servant.QQ (sitemap) import Servant.Utils.Links (mkLink) import Servant.Utils.StaticFiles diff --git a/src/Servant/Utils/ApiQuasiQuoting.hs b/src/Servant/QQ.hs similarity index 96% rename from src/Servant/Utils/ApiQuasiQuoting.hs rename to src/Servant/QQ.hs index 9337a42d..a886c90f 100644 --- a/src/Servant/Utils/ApiQuasiQuoting.hs +++ b/src/Servant/QQ.hs @@ -21,12 +21,12 @@ -- -- @ -- "hello" :> ReqBody String :> Put () --- :<|> "hello" :> Capture "p" Int :> ReqBody String :> Post () --- :<|> "hello" :> QueryParam "name" String :> Get Int +-- :\<|> "hello" :> Capture "p" Int :> ReqBody String :> Post () +-- :\<|> "hello" :> QueryParam "name" String :> Get Int -- @ -- --- Note the '/' before a 'QueryParam'! -module Servant.Utils.ApiQuasiQuoting where +-- Note the @/@ before a @QueryParam@! +module Servant.QQ where import Control.Monad (void) import Control.Applicative hiding (many, (<|>), optional) @@ -177,18 +177,14 @@ parseAll = do -- | The sitemap QuasiQuoter. -- -- * @.../:/...@ becomes a capture --- -- * @.../?:@ becomes a query parameter --- -- * @ ... @ becomes a method returning @@ --- -- * @ ... -> @ becomes a method with request -- body of @@ and returning @@ -- -- Comments are allowed, and have the standard Haskell format -- -- * @--@ for inline --- -- * @{- ... -}@ for block -- sitemap :: QuasiQuoter diff --git a/src/Servant/Utils/Links.hs b/src/Servant/Utils/Links.hs index f25533f5..547a3555 100644 --- a/src/Servant/Utils/Links.hs +++ b/src/Servant/Utils/Links.hs @@ -9,6 +9,7 @@ -- | Type safe internal links. -- -- Provides the function 'mkLink': +-- -- @ -- type API = Proxy ("hello" :> Get Int -- :<|> "bye" :> QueryParam "name" String :> Post Bool) diff --git a/test/Servant/Utils/ApiQuasiQuotingSpec.hs b/test/Servant/QQSpec.hs similarity index 99% rename from test/Servant/Utils/ApiQuasiQuotingSpec.hs rename to test/Servant/QQSpec.hs index 7dd3bfc1..29a680dc 100644 --- a/test/Servant/Utils/ApiQuasiQuotingSpec.hs +++ b/test/Servant/QQSpec.hs @@ -7,7 +7,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -module Servant.Utils.ApiQuasiQuotingSpec where +module Servant.Utils.QQSpec where import Test.Hspec From ae8cca6fb4e058b071737671433c7691a5dc50ca Mon Sep 17 00:00:00 2001 From: Alp Mestanogullari Date: Tue, 25 Nov 2014 17:35:17 +0100 Subject: [PATCH 3/3] fix renaming woes --- test/Servant/QQSpec.hs | 2 +- test/Servant/Utils/LinksSpec.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Servant/QQSpec.hs b/test/Servant/QQSpec.hs index 29a680dc..adf59611 100644 --- a/test/Servant/QQSpec.hs +++ b/test/Servant/QQSpec.hs @@ -7,7 +7,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -module Servant.Utils.QQSpec where +module Servant.QQSpec where import Test.Hspec diff --git a/test/Servant/Utils/LinksSpec.hs b/test/Servant/Utils/LinksSpec.hs index b896d7f5..3f16d71b 100644 --- a/test/Servant/Utils/LinksSpec.hs +++ b/test/Servant/Utils/LinksSpec.hs @@ -6,7 +6,7 @@ module Servant.Utils.LinksSpec where import Test.Hspec import Servant.API -import Servant.Utils.ApiQuasiQuotingSpec ( (~>) ) +import Servant.QQSpec ( (~>) ) import Servant.Utils.Links (IsElem, IsLink) type TestApi =