on master help sign in
Merge pull request #141 from haskell-servant/new-combinators
3209f98Alp Mestanogullari
servant-client/CHANGELOG.md+5
@@ -1,3 +1,8 @@
1+HEAD
2+----
3+
4+* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
5+
10.4.1
60.4.1
2-----
7-----
3* The `HasClient` instance for `Delete cts ()` now does not care at all about content types provided.
8* The `HasClient` instance for `Delete cts ()` now does not care at all about content types provided.
servant-client/src/Servant/Client.hs+17
@@ -669,3 +669,20 @@ instance (KnownSymbol path, HasClient sublayout) => HasClient (path :> sublayout
669
669
670 where p = symbolVal (Proxy :: Proxy path)
670 where p = symbolVal (Proxy :: Proxy path)
671
671
672+instance HasClient api => HasClient (Vault :> api) where
673+ type Client (Vault :> api) = Client api
674+
675+ clientWithRoute Proxy req baseurl =
676+ clientWithRoute (Proxy :: Proxy api) req baseurl
677+
678+instance HasClient api => HasClient (RemoteHost :> api) where
679+ type Client (RemoteHost :> api) = Client api
680+
681+ clientWithRoute Proxy req baseurl =
682+ clientWithRoute (Proxy :: Proxy api) req baseurl
683+
684+instance HasClient api => HasClient (IsSecure :> api) where
685+ type Client (IsSecure :> api) = Client api
686+
687+ clientWithRoute Proxy req baseurl =
688+ clientWithRoute (Proxy :: Proxy api) req baseurl
servant-docs/CHANGELOG.md+5
@@ -1,3 +1,8 @@
1+HEAD
2+----
3+
4+* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
5+
10.4
60.4
2---
7---
3* `Delete` now is like `Get`, `Post`, `Put`, and `Patch` and returns a response body
8* `Delete` now is like `Get`, `Post`, `Put`, and `Patch` and returns a response body
servant-docs/src/Servant/Docs/Internal.hs+15
@@ -900,3 +900,18 @@ instance (KnownSymbol path, HasDocs sublayout) => HasDocs (path :> sublayout) wh
900 endpoint' = endpoint & path <>~ [symbolVal pa]
900 endpoint' = endpoint & path <>~ [symbolVal pa]
901 pa = Proxy :: Proxy path
901 pa = Proxy :: Proxy path
902
902
903+instance HasDocs sublayout => HasDocs (RemoteHost :> sublayout) where
904+ docsFor Proxy ep =
905+ docsFor (Proxy :: Proxy sublayout) ep
906+
907+instance HasDocs sublayout => HasDocs (IsSecure :> sublayout) where
908+ docsFor Proxy ep =
909+ docsFor (Proxy :: Proxy sublayout) ep
910+
911+instance HasDocs sublayout => HasDocs (HttpVersion :> sublayout) where
912+ docsFor Proxy ep =
913+ docsFor (Proxy :: Proxy sublayout) ep
914+
915+instance HasDocs sublayout => HasDocs (Vault :> sublayout) where
916+ docsFor Proxy ep =
917+ docsFor (Proxy :: Proxy sublayout) ep
servant-jquery/CHANGELOG.md+5
@@ -1,3 +1,8 @@
1+HEAD
2+----
3+
4+* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
5+
10.4
60.4
2---
7---
3* `Delete` now is like `Get`, `Post`, `Put`, and `Patch` and returns a response body
8* `Delete` now is like `Get`, `Post`, `Put`, and `Patch` and returns a response body
servant-jquery/src/Servant/JQuery/Internal.hs+24
@@ -338,3 +338,27 @@ instance (KnownSymbol path, HasJQ sublayout)
338 & funcName %~ (str <>)
338 & funcName %~ (str <>)
339
339
340 where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path)
340 where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path)
341+
342+instance HasJQ sublayout => HasJQ (RemoteHost :> sublayout) where
343+ type JQ (RemoteHost :> sublayout) = JQ sublayout
344+
345+ jqueryFor Proxy req =
346+ jqueryFor (Proxy :: Proxy sublayout) req
347+
348+instance HasJQ sublayout => HasJQ (IsSecure :> sublayout) where
349+ type JQ (IsSecure :> sublayout) = JQ sublayout
350+
351+ jqueryFor Proxy req =
352+ jqueryFor (Proxy :: Proxy sublayout) req
353+
354+instance HasJQ sublayout => HasJQ (Vault :> sublayout) where
355+ type JQ (Vault :> sublayout) = JQ sublayout
356+
357+ jqueryFor Proxy req =
358+ jqueryFor (Proxy :: Proxy sublayout) req
359+
360+instance HasJQ sublayout => HasJQ (HttpVersion :> sublayout) where
361+ type JQ (HttpVersion :> sublayout) = JQ sublayout
362+
363+ jqueryFor Proxy req =
364+ jqueryFor (Proxy :: Proxy sublayout) req
servant-server/CHANGELOG.md+5
@@ -1,3 +1,8 @@
1+HEAD
2+----
3+
4+* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
5+
10.4.1
60.4.1
2-----
7-----
3* Bump attoparsec upper bound to < 0.14
8* Bump attoparsec upper bound to < 0.14
servant-server/servant-server.cabal+2
@@ -53,6 +53,7 @@ library
53 , network-uri >= 2.6 && < 2.7
53 , network-uri >= 2.6 && < 2.7
54 , mtl >= 2 && < 3
54 , mtl >= 2 && < 3
55 , mmorph >= 1
55 , mmorph >= 1
56+ , network >= 2.6 && < 2.7
56 , safe >= 0.3 && < 0.4
57 , safe >= 0.3 && < 0.4
57 , servant == 0.4.*
58 , servant == 0.4.*
58 , split >= 0.2 && < 0.3
59 , split >= 0.2 && < 0.3
@@ -64,6 +65,7 @@ library
64 , wai >= 3.0 && < 3.1
65 , wai >= 3.0 && < 3.1
65 , wai-app-static >= 3.0 && < 3.2
66 , wai-app-static >= 3.0 && < 3.2
66 , warp >= 3.0 && < 3.1
67 , warp >= 3.0 && < 3.1
68+
67 hs-source-dirs: src
69 hs-source-dirs: src
68 default-language: Haskell2010
70 default-language: Haskell2010
69 ghc-options: -Wall
71 ghc-options: -Wall
servant-server/src/Servant/Server/Internal.hs+34−6
@@ -35,15 +35,17 @@ import Data.Text.Encoding (decodeUtf8, encodeUtf8)
35import Data.Typeable
35import Data.Typeable
36import GHC.TypeLits (KnownSymbol, symbolVal)
36import GHC.TypeLits (KnownSymbol, symbolVal)
37import Network.HTTP.Types hiding (Header, ResponseHeaders)
37import Network.HTTP.Types hiding (Header, ResponseHeaders)
38+import Network.Socket (SockAddr)
38import Network.Wai (Application, lazyRequestBody,
39import Network.Wai (Application, lazyRequestBody,
39 rawQueryString, requestHeaders,
40 rawQueryString, requestHeaders,
40− requestMethod, responseLBS)
41+ requestMethod, responseLBS, remoteHost,
42+ isSecure, vault, httpVersion)
41import Servant.API ((:<|>) (..), (:>), Capture,
43import Servant.API ((:<|>) (..), (:>), Capture,
42− Delete, Get, Header,
44+ Delete, Get, Header,
43− MatrixFlag, MatrixParam, MatrixParams,
45+ IsSecure(..), MatrixFlag, MatrixParam,
44− Patch, Post, Put, QueryFlag,
46+ MatrixParams, Patch, Post, Put,
45− QueryParam, QueryParams, Raw,
47+ QueryFlag, QueryParam, QueryParams,
46− ReqBody)
48+ Raw, RemoteHost, ReqBody, Vault)
47import Servant.API.ContentTypes (AcceptHeader (..),
49import Servant.API.ContentTypes (AcceptHeader (..),
48 AllCTRender (..),
50 AllCTRender (..),
49 AllCTUnrender (..))
51 AllCTUnrender (..))
@@ -721,5 +723,31 @@ instance (KnownSymbol path, HasServer sublayout) => HasServer (path :> sublayout
721 (route (Proxy :: Proxy sublayout) subserver)
723 (route (Proxy :: Proxy sublayout) subserver)
722 where proxyPath = Proxy :: Proxy path
724 where proxyPath = Proxy :: Proxy path
723
725
726+instance HasServer api => HasServer (RemoteHost :> api) where
727+ type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
728+
729+ route Proxy subserver = WithRequest $ \req ->
730+ route (Proxy :: Proxy api) (feedTo subserver $ remoteHost req)
731+
732+instance HasServer api => HasServer (IsSecure :> api) where
733+ type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
734+
735+ route Proxy subserver = WithRequest $ \req ->
736+ route (Proxy :: Proxy api) (feedTo subserver $ secure req)
737+
738+ where secure req = if isSecure req then Secure else NotSecure
739+
740+instance HasServer api => HasServer (Vault :> api) where
741+ type ServerT (Vault :> api) m = Vault -> ServerT api m
742+
743+ route Proxy subserver = WithRequest $ \req ->
744+ route (Proxy :: Proxy api) (feedTo subserver $ vault req)
745+
746+instance HasServer api => HasServer (HttpVersion :> api) where
747+ type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
748+
749+ route Proxy subserver = WithRequest $ \req ->
750+ route (Proxy :: Proxy api) (feedTo subserver $ httpVersion req)
751+
724ct_wildcard :: B.ByteString
752ct_wildcard :: B.ByteString
725ct_wildcard = "*" <> "/" <> "*" -- Because CPP
753ct_wildcard = "*" <> "/" <> "*" -- Because CPP
servant-server/test/Servant/ServerSpec.hs+36−3
@@ -37,9 +37,9 @@ import Test.Hspec.Wai (get, liftIO, matchHeaders,
37import Servant.API ((:<|>) (..), (:>),
37import Servant.API ((:<|>) (..), (:>),
38 addHeader, Capture,
38 addHeader, Capture,
39 Delete, Get, Header (..), Headers,
39 Delete, Get, Header (..), Headers,
40− JSON, MatrixFlag, MatrixParam,
40+ HttpVersion, IsSecure(..), JSON, MatrixFlag,
41− MatrixParams, Patch, PlainText,
41+ MatrixParam, MatrixParams, Patch, PlainText,
42− Post, Put, QueryFlag, QueryParam,
42+ Post, Put, RemoteHost, QueryFlag, QueryParam,
43 QueryParams, Raw, ReqBody)
43 QueryParams, Raw, ReqBody)
44import Servant.Server (Server, serve, ServantErr(..), err404)
44import Servant.Server (Server, serve, ServantErr(..), err404)
45import Servant.Server.Internal.RoutingApplication
45import Servant.Server.Internal.RoutingApplication
@@ -93,6 +93,7 @@ spec = do
93 prioErrorsSpec
93 prioErrorsSpec
94 errorsSpec
94 errorsSpec
95 responseHeadersSpec
95 responseHeadersSpec
96+ miscReqCombinatorsSpec
96
97
97
98
98type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal
99type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal
@@ -667,3 +668,35 @@ errorsSpec = do
667 nf <> he `shouldBe` he
668 nf <> he `shouldBe` he
668 nf <> ib `shouldBe` ib
669 nf <> ib `shouldBe` ib
669 nf <> wm `shouldBe` wm
670 nf <> wm `shouldBe` wm
671+
672+type MiscCombinatorsAPI
673+ = "version" :> HttpVersion :> Get '[JSON] String
674+ :<|> "secure" :> IsSecure :> Get '[JSON] String
675+ :<|> "host" :> RemoteHost :> Get '[JSON] String
676+
677+miscApi :: Proxy MiscCombinatorsAPI
678+miscApi = Proxy
679+
680+miscServ :: Server MiscCombinatorsAPI
681+miscServ = versionHandler
682+ :<|> secureHandler
683+ :<|> hostHandler
684+
685+ where versionHandler = return . show
686+ secureHandler Secure = return "secure"
687+ secureHandler NotSecure = return "not secure"
688+ hostHandler = return . show
689+
690+miscReqCombinatorsSpec :: Spec
691+miscReqCombinatorsSpec = with (return $ serve miscApi miscServ) $
692+ describe "Misc. combinators for request inspection" $ do
693+ it "Successfully gets the HTTP version specified in the request" $
694+ go "/version" "\"HTTP/1.0\""
695+
696+ it "Checks that hspec-wai uses HTTP, not HTTPS" $
697+ go "/secure" "\"not secure\""
698+
699+ it "Checks that hspec-wai issues request from 0.0.0.0" $
700+ go "/host" "\"0.0.0.0:0\""
701+
702+ where go path res = Test.Hspec.Wai.get path `shouldRespondWith` res
servant/CHANGELOG.md+5
@@ -1,3 +1,8 @@
1+HEAD
2+----
3+
4+* Add `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
5+
10.4.2
60.4.2
2-----
7-----
3* Fix missing cases for `Patch` in `safeLink`
8* Fix missing cases for `Patch` in `safeLink`
servant/servant.cabal+5
@@ -34,15 +34,19 @@ library
34 Servant.API.Delete
34 Servant.API.Delete
35 Servant.API.Get
35 Servant.API.Get
36 Servant.API.Header
36 Servant.API.Header
37+ Servant.API.HttpVersion
38+ Servant.API.IsSecure
37 Servant.API.Patch
39 Servant.API.Patch
38 Servant.API.Post
40 Servant.API.Post
39 Servant.API.Put
41 Servant.API.Put
40 Servant.API.QueryParam
42 Servant.API.QueryParam
41 Servant.API.MatrixParam
43 Servant.API.MatrixParam
42 Servant.API.Raw
44 Servant.API.Raw
45+ Servant.API.RemoteHost
43 Servant.API.ReqBody
46 Servant.API.ReqBody
44 Servant.API.ResponseHeaders
47 Servant.API.ResponseHeaders
45 Servant.API.Sub
48 Servant.API.Sub
49+ Servant.API.Vault
46 Servant.Common.Text
50 Servant.Common.Text
47 Servant.Utils.Links
51 Servant.Utils.Links
48 build-depends:
52 build-depends:
@@ -57,6 +61,7 @@ library
57 , text >= 1 && < 2
61 , text >= 1 && < 2
58 , string-conversions >= 0.3 && < 0.5
62 , string-conversions >= 0.3 && < 0.5
59 , network-uri >= 2.6
63 , network-uri >= 2.6
64+ , vault >= 0.3 && <0.4
60 hs-source-dirs: src
65 hs-source-dirs: src
61 default-language: Haskell2010
66 default-language: Haskell2010
62 other-extensions: CPP
67 other-extensions: CPP
servant/src/Servant/API.hs+12−3
@@ -1,6 +1,3 @@
1−{-# LANGUAGE TypeFamilies #-}
2−{-# LANGUAGE TypeOperators #-}
3−{-# LANGUAGE UndecidableInstances #-}
4module Servant.API (
1module Servant.API (
5
2
6 -- * Combinators
3 -- * Combinators
@@ -14,12 +11,20 @@ module Servant.API (
14 -- | Capturing parts of the url path as parsed values: @'Capture'@
11 -- | Capturing parts of the url path as parsed values: @'Capture'@
15 module Servant.API.Header,
12 module Servant.API.Header,
16 -- | Retrieving specific headers from the request
13 -- | Retrieving specific headers from the request
14+ module Servant.API.HttpVersion,
15+ -- | Retrieving the HTTP version of the request
17 module Servant.API.QueryParam,
16 module Servant.API.QueryParam,
18 -- | Retrieving parameters from the query string of the 'URI': @'QueryParam'@
17 -- | Retrieving parameters from the query string of the 'URI': @'QueryParam'@
19 module Servant.API.ReqBody,
18 module Servant.API.ReqBody,
20 -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
19 -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
21 module Servant.API.MatrixParam,
20 module Servant.API.MatrixParam,
22 -- | Retrieving matrix parameters from the 'URI' segment: @'MatrixParam'@
21 -- | Retrieving matrix parameters from the 'URI' segment: @'MatrixParam'@
22+ module Servant.API.RemoteHost,
23+ -- | Retrieving the IP of the client
24+ module Servant.API.IsSecure,
25+ -- | Is the request made through HTTPS?
26+ module Servant.API.Vault,
27+ -- | Access the location for arbitrary data to be shared by applications and middleware
23
28
24 -- * Actual endpoints, distinguished by HTTP method
29 -- * Actual endpoints, distinguished by HTTP method
25 module Servant.API.Get,
30 module Servant.API.Get,
@@ -64,6 +69,8 @@ import Servant.API.ContentTypes (Accept (..), FormUrlEncoded,
64import Servant.API.Delete (Delete)
69import Servant.API.Delete (Delete)
65import Servant.API.Get (Get)
70import Servant.API.Get (Get)
66import Servant.API.Header (Header (..))
71import Servant.API.Header (Header (..))
72+import Servant.API.HttpVersion (HttpVersion(..))
73+import Servant.API.IsSecure (IsSecure(..))
67import Servant.API.MatrixParam (MatrixFlag, MatrixParam,
74import Servant.API.MatrixParam (MatrixFlag, MatrixParam,
68 MatrixParams)
75 MatrixParams)
69import Servant.API.Patch (Patch)
76import Servant.API.Patch (Patch)
@@ -72,6 +79,7 @@ import Servant.API.Put (Put)
72import Servant.API.QueryParam (QueryFlag, QueryParam,
79import Servant.API.QueryParam (QueryFlag, QueryParam,
73 QueryParams)
80 QueryParams)
74import Servant.API.Raw (Raw)
81import Servant.API.Raw (Raw)
82+import Servant.API.RemoteHost (RemoteHost)
75import Servant.API.ReqBody (ReqBody)
83import Servant.API.ReqBody (ReqBody)
76import Servant.API.ResponseHeaders (AddHeader (addHeader),
84import Servant.API.ResponseHeaders (AddHeader (addHeader),
77 BuildHeadersTo (buildHeadersTo),
85 BuildHeadersTo (buildHeadersTo),
@@ -79,6 +87,7 @@ import Servant.API.ResponseHeaders (AddHeader (addHeader),
79 HList (..), Headers (..),
87 HList (..), Headers (..),
80 getHeadersHList, getResponse)
88 getHeadersHList, getResponse)
81import Servant.API.Sub ((:>))
89import Servant.API.Sub ((:>))
90+import Servant.API.Vault (Vault)
82import Servant.Common.Text (FromText (..), ToText (..))
91import Servant.Common.Text (FromText (..), ToText (..))
83import Servant.Utils.Links (HasLink (..), IsElem, IsElem',
92import Servant.Utils.Links (HasLink (..), IsElem, IsElem',
84 URI (..), safeLink)
93 URI (..), safeLink)
servant/src/Servant/API/HttpVersion.hsnew file+19
@@ -0,0 +1,19 @@
1+module Servant.API.HttpVersion
2+ ( -- $httpversion
3+ HttpVersion(..)
4+ ) where
5+
6+import Network.HTTP.Types (HttpVersion(..))
7+
8+-- $httpversion
9+--
10+-- | You can directly use the 'HttpVersion' type from @Network.HTTP.Types@
11+-- if your request handlers need it to compute a response. This would
12+-- make the request handlers take an argument of type 'HttpVersion'.
13+--
14+-- Example:
15+--
16+-- >>> type API = HttpVersion :> Get '[JSON] String
17+
18+-- $setup
19+-- >>> import Servant.API
servant/src/Servant/API/IsSecure.hsnew file+37
@@ -0,0 +1,37 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+module Servant.API.IsSecure
3+ ( -- $issecure
4+ IsSecure(..)
5+ ) where
6+
7+import Data.Typeable
8+
9+-- | Was this request made over an SSL connection?
10+--
11+-- Note that this value will not tell you if the client originally
12+-- made this request over SSL, but rather whether the current
13+-- connection is SSL. The distinction lies with reverse proxies.
14+-- In many cases, the client will connect to a load balancer over SSL,
15+-- but connect to the WAI handler without SSL. In such a case,
16+-- the handlers would get 'NotSecure', but from a user perspective,
17+-- there is a secure connection.
18+data IsSecure = Secure -- ^ the connection to the server
19+ -- is secure (HTTPS)
20+ | NotSecure -- ^ the connection to the server
21+ -- is not secure (HTTP)
22+ deriving Typeable
23+
24+-- $issecure
25+--
26+-- | Use 'IsSecure' whenever your request handlers need to know whether
27+-- the connection to the server is secure or not.
28+-- This would make the request handlers receive an argument of type 'IsSecure',
29+-- whose value can be one of 'Secure' (HTTPS) or 'NotSecure' (HTTP).
30+--
31+-- Example:
32+--
33+-- >>> type API = "sensitive-data" :> IsSecure :> Get '[JSON] NationSecrets
34+
35+-- $setup
36+-- >>> import Servant.API
37+-- >>> data NationSecrets
servant/src/Servant/API/RemoteHost.hsnew file+25
@@ -0,0 +1,25 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+module Servant.API.RemoteHost
3+ ( -- $remotehost
4+ RemoteHost
5+ ) where
6+
7+import Data.Typeable
8+
9+-- | Provides access to the host or IP address
10+-- from which the HTTP request was sent.
11+data RemoteHost deriving Typeable
12+
13+-- $remotehost
14+--
15+-- | Use 'RemoteHost' whenever your request handlers need the host or IP address
16+-- from which the client issued the HTTP request. The corresponding handlers
17+-- receive arguments of type @SockAddr@ (from @Network.Socket@).
18+--
19+-- Example:
20+--
21+-- >>> -- POST /record-ip
22+-- >>> type API = "record-ip" :> RemoteHost :> Post '[] ()
23+
24+-- $setup
25+-- >>> import Servant.API
servant/src/Servant/API/Vault.hsnew file+20
@@ -0,0 +1,20 @@
1+module Servant.API.Vault
2+ ( -- $vault
3+ Vault
4+ ) where
5+
6+import Data.Vault.Lazy (Vault)
7+
8+-- $vault
9+--
10+-- | Use 'Vault' in your API types to provide access to the 'Vault'
11+-- of the request, which is a location shared by middlewares and applications
12+-- to store arbitrary data. See 'Vault' for more details on how to actually
13+-- use the vault in your handlers
14+--
15+-- Example:
16+--
17+-- >>> type API = Vault :> Get '[JSON] String
18+
19+-- $setup
20+-- >>> import Servant.API