on master help sign in
add HasClient instance for Header
commit2ecc6124b0a77c15fafd900116c778bc7880af5d authorAlp Mestanogullari <alpmestan@gmail.com> dateMon 8 Dec 2014, 12:52 (12 years ago) parentfe3bd99
2 files changed+60−9
src/Servant/Client.hs+43−6 src/Servant/Common/Req.hs+17−3
src/Servant/Client.hs+43−6
@@ -21,7 +21,7 @@ import Data.Proxy
2121import Data.String.Conversions
2222import Data.Text (unpack)
2323import GHC.TypeLits
24−import Network.HTTP.Types
24+import qualified Network.HTTP.Types as H
2525import Servant.API
2626import Servant.Common.BaseUrl
2727import Servant.Common.Req
@@ -108,7 +108,7 @@ instance HasClient Delete where
108108 type Client Delete = BaseUrl -> EitherT String IO ()
109109
110110 clientWithRoute Proxy req host =
111− performRequestJSON methodDelete req 204 host
111+ performRequestJSON H.methodDelete req 204 host
112112
113113-- | If you have a 'Get' endpoint in your API, the client
114114-- side querying function that is created when calling 'client'
@@ -117,7 +117,44 @@ instance HasClient Delete where
117117instance FromJSON result => HasClient (Get result) where
118118 type Client (Get result) = BaseUrl -> EitherT String IO result
119119 clientWithRoute Proxy req host =
120− performRequestJSON methodGet req 200 host
120+ performRequestJSON H.methodGet req 200 host
121+
122+-- | If you use a 'Header' in one of your endpoints in your API,
123+-- the corresponding querying function will automatically take
124+-- an additional argument of the type specified by your 'Header',
125+-- wrapped in Maybe.
126+--
127+-- That function will take care of encoding this argument as Text
128+-- in the request headers.
129+--
130+-- All you need is for your type to have a 'ToText' instance.
131+--
132+-- Example:
133+--
134+-- > newtype Referer = Referer Text
135+-- > deriving (Eq, Show, FromText, ToText)
136+-- >
137+-- > -- GET /view-my-referer
138+-- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get Referer
139+-- >
140+-- > myApi :: Proxy MyApi
141+-- > myApi = Proxy
142+-- >
143+-- > viewReferer :: Maybe Referer -> BaseUrl -> EitherT String IO Book
144+-- > viewReferer = client myApi
145+-- > -- then you can just use "viewRefer" to query that endpoint
146+-- > -- specifying Nothing or Just "http://haskell.org/" as arguments
147+instance (KnownSymbol sym, ToText a, HasClient sublayout)
148+ => HasClient (Header sym a :> sublayout) where
149+
150+ type Client (Header sym a :> sublayout) =
151+ Maybe a -> Client sublayout
152+
153+ clientWithRoute Proxy req mval =
154+ clientWithRoute (Proxy :: Proxy sublayout) $
155+ maybe req (\value -> addHeader hname value req) mval
156+
157+ where hname = symbolVal (Proxy :: Proxy sym)
121158
122159-- | If you have a 'Post' endpoint in your API, the client
123160-- side querying function that is created when calling 'client'
@@ -127,7 +164,7 @@ instance FromJSON a => HasClient (Post a) where
127164 type Client (Post a) = BaseUrl -> EitherT String IO a
128165
129166 clientWithRoute Proxy req uri =
130− performRequestJSON methodPost req 201 uri
167+ performRequestJSON H.methodPost req 201 uri
131168
132169-- | If you have a 'Put' endpoint in your API, the client
133170-- side querying function that is created when calling 'client'
@@ -137,7 +174,7 @@ instance FromJSON a => HasClient (Put a) where
137174 type Client (Put a) = BaseUrl -> EitherT String IO a
138175
139176 clientWithRoute Proxy req host =
140− performRequestJSON methodPut req 200 host
177+ performRequestJSON H.methodPut req 200 host
141178
142179-- | If you use a 'QueryParam' in one of your endpoints in your API,
143180-- the corresponding querying function will automatically take
@@ -258,7 +295,7 @@ instance (KnownSymbol sym, HasClient sublayout)
258295-- | Pick a 'Method' and specify where the server you want to query is. You get
259296-- back the status code and the response body as a 'ByteString'.
260297instance HasClient Raw where
261− type Client Raw = Method -> BaseUrl -> EitherT String IO (Int, ByteString)
298+ type Client Raw = H.Method -> BaseUrl -> EitherT String IO (Int, ByteString)
262299
263300 clientWithRoute :: Proxy Raw -> Req -> Client Raw
264301 clientWithRoute Proxy req httpMethod host =
src/Servant/Common/Req.hs+17−3
@@ -13,13 +13,16 @@ import Data.Aeson
1313import Data.Aeson.Parser
1414import Data.Aeson.Types
1515import Data.Attoparsec.ByteString
16−import Data.ByteString.Lazy
16+import Data.ByteString.Lazy hiding (pack)
17+import Data.String
1718import Data.String.Conversions
1819import Data.Text
20+import Data.Text.Encoding
1921import Network.HTTP.Client
2022import Network.HTTP.Types
2123import Network.URI
2224import Servant.Common.BaseUrl
25+import Servant.Common.Text
2326import System.IO.Unsafe
2427
2528import qualified Network.HTTP.Client as Client
@@ -28,10 +31,11 @@ data Req = Req
2831 { reqPath :: String
2932 , qs :: QueryText
3033 , reqBody :: ByteString
34+ , headers :: [(String, Text)]
3135 }
3236
3337defReq :: Req
34−defReq = Req "" [] ""
38+defReq = Req "" [] "" []
3539
3640appendToPath :: String -> Req -> Req
3741appendToPath p req =
@@ -45,11 +49,17 @@ appendToQueryString pname pvalue req =
4549 req { qs = qs req ++ [(pname, pvalue)]
4650 }
4751
52+addHeader :: ToText a => String -> a -> Req -> Req
53+addHeader name val req = req { headers = headers req
54+ ++ [(name, toText val)]
55+ }
56+
4857setRQBody :: ByteString -> Req -> Req
4958setRQBody b req = req { reqBody = b }
5059
5160reqToRequest :: (Functor m, MonadThrow m) => Req -> BaseUrl -> m Request
52−reqToRequest req (BaseUrl reqScheme reqHost reqPort) = fmap (setrqb . setQS ) $ parseUrl url
61+reqToRequest req (BaseUrl reqScheme reqHost reqPort) =
62+ fmap (setheaders . setrqb . setQS ) $ parseUrl url
5363
5464 where url = show $ nullURI { uriScheme = case reqScheme of
5565 Http -> "http:"
@@ -64,6 +74,10 @@ reqToRequest req (BaseUrl reqScheme reqHost reqPort) = fmap (setrqb . setQS ) $
6474
6575 setrqb r = r { requestBody = RequestBodyLBS (reqBody req) }
6676 setQS = setQueryString $ queryTextToQuery (qs req)
77+ setheaders r = r { requestHeaders = Prelude.map toProperHeader (headers req) }
78+
79+ toProperHeader (name, val) =
80+ (fromString name, encodeUtf8 val)
6781
6882
6983-- * performing requests