@@ -26,14 +26,13 @@ import Prelude ()
2626import Prelude.Compat
2727
2828import Control.Arrow (left)
29−import Control.Monad.Trans.Except (runExceptT, throwE)
29+import Control.Monad.Trans.Except (throwE )
3030import Data.Aeson
3131import qualified Data.ByteString.Lazy as BS
3232import Data.Char (chr, isPrint)
3333import Data.Foldable (forM_)
3434import Data.Monoid hiding (getLast)
3535import Data.Proxy
36−import qualified Data.Text as T
3736import GHC.Generics (Generic)
3837import qualified Network.HTTP.Client as C
3938import Network.HTTP.Media
@@ -46,10 +45,12 @@ import Test.HUnit
4645import Test.Hspec
4746import Test.Hspec.QuickCheck
4847import Test.QuickCheck
48+import Web.FormUrlEncoded (FromForm, ToForm)
4949
5050import Servant.API
5151import Servant.Client
5252import Servant.Client.TestServer
53+import Servant.Client.TestServer.GHC
5354import qualified Servant.Common.Req as SCR
5455import Servant.Server
5556import Servant.Server.Experimental.Auth
@@ -117,19 +118,8 @@ data Person = Person {
117118instance ToJSON Person
118119instance FromJSON Person
119120
120−instance ToFormUrlEncoded Person where
121− toFormUrlEncoded Person{..} =
122− [("name", T.pack name), ("age", T.pack (show age))]
123−
124−lookupEither :: (Show a, Eq a) => a -> [(a,b)] -> Either String b
125−lookupEither x xs = do
126− maybe (Left $ "could not find key " <> show x) return $ lookup x xs
127−
128−instance FromFormUrlEncoded Person where
129− fromFormUrlEncoded xs = do
130− n <- lookupEither "name" xs
131− a <- lookupEither "age" xs
132− return $ Person (T.unpack n) (read $ T.unpack a)
121+instance ToForm Person where
122+instance FromForm Person where
133123
134124alice :: Person
135125alice = Person "Alice" 42
@@ -140,6 +130,7 @@ type Api =
140130 "get" :> Get '[JSON] Person
141131 :<|> "deleteEmpty" :> DeleteNoContent '[JSON] NoContent
142132 :<|> "capture" :> Capture "name" String :> Get '[JSON,FormUrlEncoded] Person
133+ :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] [Person]
143134 :<|> "body" :> ReqBody '[FormUrlEncoded,JSON] Person :> Post '[JSON] Person
144135 :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person
145136 :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
@@ -157,24 +148,26 @@ type Api =
157148api :: Proxy Api
158149api = Proxy
159150
160−getGet :: C.Manager -> BaseUrl -> SCR.ClientM Person
161−getDeleteEmpty :: C.Manager -> BaseUrl -> SCR.ClientM NoContent
162−getCapture :: String -> C.Manager -> BaseUrl -> SCR.ClientM Person
163−getBody :: Person -> C.Manager -> BaseUrl -> SCR.ClientM Person
164−getQueryParam :: Maybe String -> C.Manager -> BaseUrl -> SCR.ClientM Person
165−getQueryParams :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM [Person]
166−getQueryFlag :: Bool -> C.Manager -> BaseUrl -> SCR.ClientM Bool
167−getRawSuccess :: HTTP.Method -> C.Manager -> BaseUrl
151+getGet :: SCR.ClientM Person
152+getDeleteEmpty :: SCR.ClientM NoContent
153+getCapture :: String -> SCR.ClientM Person
154+getCaptureAll :: [String] -> SCR.ClientM [Person]
155+getBody :: Person -> SCR.ClientM Person
156+getQueryParam :: Maybe String -> SCR.ClientM Person
157+getQueryParams :: [String] -> SCR.ClientM [Person]
158+getQueryFlag :: Bool -> SCR.ClientM Bool
159+getRawSuccess :: HTTP.Method
168160 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
169−getRawFailure :: HTTP.Method -> C.Manager -> BaseUrl
161+getRawFailure :: HTTP.Method
170162 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
171−getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])] -> C.Manager -> BaseUrl
163+getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
172164 -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])])
173−getRespHeaders :: C.Manager -> BaseUrl -> SCR.ClientM (Headers TestHeaders Bool)
174−getDeleteContentType :: C.Manager -> BaseUrl -> SCR.ClientM NoContent
165+getRespHeaders :: SCR.ClientM (Headers TestHeaders Bool)
166+getDeleteContentType :: SCR.ClientM NoContent
175167getGet
176168 :<|> getDeleteEmpty
177169 :<|> getCapture
170+ :<|> getCaptureAll
178171 :<|> getBody
179172 :<|> getQueryParam
180173 :<|> getQueryParams
@@ -190,6 +183,7 @@ server = TestServer "server" $ serve api (
190183 return alice
191184 :<|> return NoContent
192185 :<|> (\ name -> return $ Person name 0)
186+ :<|> (\ names -> return (zipWith Person names [0..]))
193187 :<|> return
194188 :<|> (\ name -> case name of
195189 Just "alice" -> return alice
@@ -275,38 +269,42 @@ sucessSpec :: Spec
275269sucessSpec = around (withTestServer "server") $ do
276270
277271 it "Servant.API.Get" $ \baseUrl -> do
278− (left show <$> runExceptT (getGet manager baseUrl)) `shouldReturn` Right alice
272+ (left show <$> (runClientM getGet (ClientEnv manager baseUrl))) `shouldReturn` Right alice
279273
280274 describe "Servant.API.Delete" $ do
281275 it "allows empty content type" $ \baseUrl -> do
282− (left show <$> runExceptT (getDeleteEmpty manager baseUrl)) `shouldReturn` Right NoContent
276+ (left show <$> (runClientM getDeleteEmpty (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
283277
284278 it "allows content type" $ \baseUrl -> do
285− (left show <$> runExceptT (getDeleteContentType manager baseUrl)) `shouldReturn` Right NoContent
279+ (left show <$> (runClientM getDeleteContentType (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
286280
287281 it "Servant.API.Capture" $ \baseUrl -> do
288− (left show <$> runExceptT (getCapture "Paula" manager baseUrl)) `shouldReturn` Right (Person "Paula" 0)
282+ (left show <$> (runClientM (getCapture "Paula") (ClientEnv manager baseUrl))) `shouldReturn` Right (Person "Paula" 0)
283+
284+ it "Servant.API.CaptureAll" $ \baseUrl -> do
285+ let expected = [(Person "Paula" 0), (Person "Peta" 1)]
286+ (left show <$> (runClientM (getCaptureAll ["Paula", "Peta"]) (ClientEnv manager baseUrl))) `shouldReturn` Right expected
289287
290288 it "Servant.API.ReqBody" $ \baseUrl -> do
291289 let p = Person "Clara" 42
292− (left show <$> runExceptT (getBody p manager baseUrl)) `shouldReturn` Right p
290+ (left show <$> runClientM (getBody p) (ClientEnv manager baseUrl)) `shouldReturn` Right p
293291
294292 it "Servant.API.QueryParam" $ \baseUrl -> do
295− left show <$> runExceptT (getQueryParam (Just "alice") manager baseUrl) `shouldReturn` Right alice
296− Left FailureResponse{..} <- runExceptT (getQueryParam (Just "bob") manager baseUrl)
293+ left show <$> runClientM (getQueryParam (Just "alice")) (ClientEnv manager baseUrl) `shouldReturn` Right alice
294+ Left FailureResponse{..} <- runClientM (getQueryParam (Just "bob")) (ClientEnv manager baseUrl)
297295 responseStatus `shouldBe` HTTP.Status 400 "bob not found"
298296
299297 it "Servant.API.QueryParam.QueryParams" $ \baseUrl -> do
300− (left show <$> runExceptT (getQueryParams [] manager baseUrl)) `shouldReturn` Right []
301− (left show <$> runExceptT (getQueryParams ["alice", "bob"] manager baseUrl))
298+ (left show <$> runClientM (getQueryParams []) (ClientEnv manager baseUrl)) `shouldReturn` Right []
299+ (left show <$> runClientM (getQueryParams ["alice", "bob"]) (ClientEnv manager baseUrl))
302300 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
303301
304302 context "Servant.API.QueryParam.QueryFlag" $
305303 forM_ [False, True] $ \ flag -> it (show flag) $ \baseUrl -> do
306− (left show <$> runExceptT (getQueryFlag flag manager baseUrl)) `shouldReturn` Right flag
304+ (left show <$> runClientM (getQueryFlag flag) (ClientEnv manager baseUrl)) `shouldReturn` Right flag
307305
308306 it "Servant.API.Raw on success" $ \baseUrl -> do
309− res <- runExceptT (getRawSuccess HTTP.methodGet manager baseUrl)
307+ res <- runClientM (getRawSuccess HTTP.methodGet) (ClientEnv manager baseUrl)
310308 case res of
311309 Left e -> assertFailure $ show e
312310 Right (code, body, ct, _, response) -> do
@@ -315,7 +313,7 @@ sucessSpec = around (withTestServer "server") $ do
315313 C.responseStatus response `shouldBe` HTTP.ok200
316314
317315 it "Servant.API.Raw should return a Left in case of failure" $ \baseUrl -> do
318− res <- runExceptT (getRawFailure HTTP.methodGet manager baseUrl)
316+ res <- runClientM (getRawFailure HTTP.methodGet) (ClientEnv manager baseUrl)
319317 case res of
320318 Right _ -> assertFailure "expected Left, but got Right"
321319 Left e -> do
@@ -323,7 +321,7 @@ sucessSpec = around (withTestServer "server") $ do
323321 Servant.Client.responseBody e `shouldBe` "rawFailure"
324322
325323 it "Returns headers appropriately" $ \baseUrl -> do
326− res <- runExceptT (getRespHeaders manager baseUrl)
324+ res <- runClientM getRespHeaders (ClientEnv manager baseUrl)
327325 case res of
328326 Left e -> assertFailure $ show e
329327 Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
@@ -332,7 +330,7 @@ sucessSpec = around (withTestServer "server") $ do
332330 it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \baseUrl ->
333331 property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
334332 ioProperty $ do
335− result <- left show <$> runExceptT (getMultiple cap num flag body manager baseUrl)
333+ result <- left show <$> runClientM (getMultiple cap num flag body) (ClientEnv manager baseUrl)
336334 return $
337335 result === Right (cap, num, flag, body)
338336
@@ -358,9 +356,9 @@ errorSpec =
358356 it "reports error statuses correctly" $ \baseUrl -> do
359357 let delete :<|> get :<|> post :<|> put =
360358 client errorApi
361− actions = map (\ f -> f manager baseUrl) [delete, get, post, put]
359+ actions = [delete, get, post, put]
362360 forM_ actions $ \ clientAction -> do
363− Left FailureResponse{..} <- runExceptT clientAction
361+ Left FailureResponse{..} <- runClientM clientAction (ClientEnv manager baseUrl)
364362 responseStatus `shouldBe` HTTP.Status 500 "error message"
365363
366364basicAuthSpec :: Spec
@@ -370,14 +368,14 @@ basicAuthSpec = around (withTestServer "basicAuthServer") $ do
370368 it "Authenticates a BasicAuth protected server appropriately" $ \baseUrl -> do
371369 let getBasic = client basicAuthAPI
372370 let basicAuthData = BasicAuthData "servant" "server"
373− (left show <$> runExceptT (getBasic basicAuthData manager baseUrl)) `shouldReturn` Right alice
371+ (left show <$> runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
374372
375373 context "Authentication is rejected when requests are not authenticated properly" $ do
376374
377375 it "Authenticates a BasicAuth protected server appropriately" $ \baseUrl -> do
378376 let getBasic = client basicAuthAPI
379377 let basicAuthData = BasicAuthData "not" "password"
380− Left FailureResponse{..} <- runExceptT (getBasic basicAuthData manager baseUrl)
378+ Left FailureResponse{..} <- runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)
381379 responseStatus `shouldBe` HTTP.Status 403 "Forbidden"
382380
383381genAuthSpec :: Spec
@@ -387,14 +385,14 @@ genAuthSpec = around (withTestServer "genAuthServer") $ do
387385 it "Authenticates a AuthProtect protected server appropriately" $ \baseUrl -> do
388386 let getProtected = client genAuthAPI
389387 let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "AuthHeader" ("cool" :: String) req)
390− (left show <$> runExceptT (getProtected authRequest manager baseUrl)) `shouldReturn` Right alice
388+ (left show <$> runClientM (getProtected authRequest) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
391389
392390 context "Authentication is rejected when requests are not authenticated properly" $ do
393391
394392 it "Authenticates a AuthProtect protected server appropriately" $ \baseUrl -> do
395393 let getProtected = client genAuthAPI
396394 let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "Wrong" ("header" :: String) req)
397− Left FailureResponse{..} <- runExceptT (getProtected authRequest manager baseUrl)
395+ Left FailureResponse{..} <- runClientM (getProtected authRequest) (ClientEnv manager baseUrl)
398396 responseStatus `shouldBe` (HTTP.Status 401 "Unauthorized")
399397
400398failSpec :: Spec
@@ -403,35 +401,36 @@ failSpec = around (withTestServer "failServer") $ do
403401 context "client returns errors appropriately" $ do
404402 it "reports FailureResponse" $ \baseUrl -> do
405403 let (_ :<|> getDeleteEmpty :<|> _) = client api
406− Left res <- runExceptT (getDeleteEmpty manager baseUrl)
404+ Left res <- runClientM getDeleteEmpty (ClientEnv manager baseUrl)
407405 case res of
408406 FailureResponse (HTTP.Status 404 "Not Found") _ _ -> return ()
409407 _ -> fail $ "expected 404 response, but got " <> show res
410408
411409 it "reports DecodeFailure" $ \baseUrl -> do
412410 let (_ :<|> _ :<|> getCapture :<|> _) = client api
413− Left res <- runExceptT (getCapture "foo" manager baseUrl)
411+ Left res <- runClientM (getCapture "foo") (ClientEnv manager baseUrl)
414412 case res of
415413 DecodeFailure _ ("application/json") _ -> return ()
416414 _ -> fail $ "expected DecodeFailure, but got " <> show res
417415
418416 it "reports ConnectionError" $ \_ -> do
419417 let (getGetWrongHost :<|> _) = client api
420− Left res <- runExceptT (getGetWrongHost manager (BaseUrl Http "127.0.0.1" 19872 ""))
418+ Left res <- runClientM getGetWrongHost (ClientEnv manager (BaseUrl Http "127.0.0.1" 19872 ""))
421419 case res of
422420 ConnectionError _ -> return ()
423421 _ -> fail $ "expected ConnectionError, but got " <> show res
424422
425423 it "reports UnsupportedContentType" $ \baseUrl -> do
426424 let (getGet :<|> _ ) = client api
427− Left res <- runExceptT (getGet manager baseUrl)
425+ Left res <- runClientM getGet (ClientEnv manager baseUrl)
428426 case res of
429427 UnsupportedContentType ("application/octet-stream") _ -> return ()
430428 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
431429
432430 it "reports InvalidContentTypeHeader" $ \baseUrl -> do
433− let (_ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
434− Left res <- runExceptT (getBody alice manager baseUrl)
431+ let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
432+ Left res <- runClientM (getBody alice) (ClientEnv manager baseUrl)
433+
435434 case res of
436435 InvalidContentTypeHeader "fooooo" _ -> return ()
437436 _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res