From a8e1315ee7e1ebc67578ece62b5c226516570640 Mon Sep 17 00:00:00 2001 From: Joseph Kachmar Date: Tue, 5 Jul 2016 11:34:30 -0400 Subject: [PATCH 01/43] Fix servant-js examples The Servant.JS Counter example was broken: - `axios` was not completely applied. It now uses `defAxiosOptions` similarly to the `angular` example - `OverloadedStrings` was required, but not included --- servant-js/examples/counter.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/servant-js/examples/counter.hs b/servant-js/examples/counter.hs index 5d2b80d0..4040b053 100644 --- a/servant-js/examples/counter.hs +++ b/servant-js/examples/counter.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} import Control.Concurrent.STM @@ -92,7 +93,7 @@ main = do writeJSForAPI testApi (angular defAngularOptions) (www "angular" "api.js") - writeJSForAPI testApi axios (www "axios" "api.js") + writeJSForAPI testApi (axios defAxiosOptions) (www "axios" "api.js") writeServiceJS (www "angular" "api.service.js") From a616a8d6898363e16b8ef658c68ac9481054c0d4 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 26 May 2016 16:49:04 +0100 Subject: [PATCH 02/43] Add CaptureAll to the API definitions --- servant/src/Servant/API.hs | 4 ++-- servant/src/Servant/API/Capture.hs | 15 ++++++++++++++- servant/src/Servant/Utils/Links.hs | 11 ++++++++++- servant/test/Servant/Utils/LinksSpec.hs | 5 +++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/servant/src/Servant/API.hs b/servant/src/Servant/API.hs index 5ea7b480..cbb0db09 100644 --- a/servant/src/Servant/API.hs +++ b/servant/src/Servant/API.hs @@ -8,7 +8,7 @@ module Servant.API ( -- * Accessing information from the request module Servant.API.Capture, - -- | Capturing parts of the url path as parsed values: @'Capture'@ + -- | Capturing parts of the url path as parsed values: @'Capture'@ and @'CaptureAll'@ module Servant.API.Header, -- | Retrieving specific headers from the request module Servant.API.HttpVersion, @@ -60,7 +60,7 @@ module Servant.API ( import Servant.API.Alternative ((:<|>) (..)) import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..)) -import Servant.API.Capture (Capture) +import Servant.API.Capture (Capture, CaptureAll) import Servant.API.ContentTypes (Accept (..), FormUrlEncoded, FromFormUrlEncoded (..), JSON, MimeRender (..), NoContent (NoContent), diff --git a/servant/src/Servant/API/Capture.hs b/servant/src/Servant/API/Capture.hs index 9a2e1b61..7ee7972a 100644 --- a/servant/src/Servant/API/Capture.hs +++ b/servant/src/Servant/API/Capture.hs @@ -2,7 +2,7 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE PolyKinds #-} {-# OPTIONS_HADDOCK not-home #-} -module Servant.API.Capture (Capture) where +module Servant.API.Capture (Capture, CaptureAll) where import Data.Typeable (Typeable) import GHC.TypeLits (Symbol) @@ -15,9 +15,22 @@ import GHC.TypeLits (Symbol) data Capture (sym :: Symbol) a deriving (Typeable) + +-- | Capture all remaining values from the request path under a certain type +-- @a@. +-- +-- Example: +-- +-- >>> -- GET /src/* +-- >>> type MyAPI = "src" :> CaptureAll "segments" Text :> Get '[JSON] SourceFile +data CaptureAll (sym :: Symbol) a + deriving (Typeable) + -- $setup -- >>> import Servant.API -- >>> import Data.Aeson -- >>> import Data.Text -- >>> data Book -- >>> instance ToJSON Book where { toJSON = undefined } +-- >>> data SourceFile +-- >>> instance ToJSON SourceFile where { toJSON = undefined } diff --git a/servant/src/Servant/Utils/Links.hs b/servant/src/Servant/Utils/Links.hs index c312997c..d6b218be 100644 --- a/servant/src/Servant/Utils/Links.hs +++ b/servant/src/Servant/Utils/Links.hs @@ -107,7 +107,7 @@ import Prelude.Compat import Web.HttpApiData import Servant.API.BasicAuth ( BasicAuth ) -import Servant.API.Capture ( Capture ) +import Servant.API.Capture ( Capture, CaptureAll ) import Servant.API.ReqBody ( ReqBody ) import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag ) import Servant.API.Header ( Header ) @@ -163,6 +163,8 @@ type family IsElem endpoint api :: Constraint where IsElem sa (ReqBody y x :> sb) = IsElem sa sb IsElem (Capture z y :> sa) (Capture x y :> sb) = IsElem sa sb + IsElem (CaptureAll z y :> sa) (CaptureAll x y :> sb) + = IsElem sa sb IsElem sa (QueryParam x y :> sb) = IsElem sa sb IsElem sa (QueryParams x y :> sb) = IsElem sa sb IsElem sa (QueryFlag x :> sb) = IsElem sa sb @@ -284,6 +286,13 @@ instance (ToHttpApiData v, HasLink sub) toLink (Proxy :: Proxy sub) $ addSegment (escape . Text.unpack $ toUrlPiece v) l +instance (ToHttpApiData v, HasLink sub) + => HasLink (CaptureAll sym v :> sub) where + type MkLink (CaptureAll sym v :> sub) = [v] -> MkLink sub + toLink _ l vs = + toLink (Proxy :: Proxy sub) $ + foldl' (flip $ addSegment . escape . Text.unpack . toUrlPiece) l vs + instance HasLink sub => HasLink (Header sym a :> sub) where type MkLink (Header sym a :> sub) = MkLink sub toLink _ = toLink (Proxy :: Proxy sub) diff --git a/servant/test/Servant/Utils/LinksSpec.hs b/servant/test/Servant/Utils/LinksSpec.hs index 5a7ea4c4..2040fc55 100644 --- a/servant/test/Servant/Utils/LinksSpec.hs +++ b/servant/test/Servant/Utils/LinksSpec.hs @@ -13,6 +13,7 @@ import Servant.API type TestApi = -- Capture and query params "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Delete '[JSON] NoContent + :<|> "all" :> CaptureAll "names" String :> Get '[JSON] NoContent -- Flags :<|> "balls" :> QueryFlag "bouncy" :> QueryFlag "fast" :> Delete '[JSON] NoContent @@ -46,6 +47,10 @@ spec = describe "Servant.Utils.Links" $ do :> Delete '[JSON] NoContent) apiLink l2 "bye" (Just True) `shouldBeURI` "hello/bye?capital=true" + it "generates correct links for CaptureAll" $ do + apiLink (Proxy :: Proxy ("all" :> CaptureAll "names" String :> Get '[JSON] NoContent)) + ["roads", "lead", "to", "rome"] + `shouldBeURI` "all/roads/lead/to/rome" it "generates correct links for query flags" $ do let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy" From 0d8a5b5afff7a99bbba3970691d703a79003fb9d Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 26 May 2016 16:55:02 +0100 Subject: [PATCH 03/43] servant-mock support for CaptureAll --- servant-mock/src/Servant/Mock.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/servant-mock/src/Servant/Mock.hs b/servant-mock/src/Servant/Mock.hs index 8aa57f0f..0d0f4a48 100644 --- a/servant-mock/src/Servant/Mock.hs +++ b/servant-mock/src/Servant/Mock.hs @@ -115,6 +115,9 @@ instance (KnownSymbol path, HasMock rest context) => HasMock (path :> rest) cont instance (KnownSymbol s, FromHttpApiData a, HasMock rest context) => HasMock (Capture s a :> rest) context where mock _ context = \_ -> mock (Proxy :: Proxy rest) context +instance (KnownSymbol s, FromHttpApiData a, HasMock rest context) => HasMock (CaptureAll s a :> rest) context where + mock _ context = \_ -> mock (Proxy :: Proxy rest) context + instance (AllCTUnrender ctypes a, HasMock rest context) => HasMock (ReqBody ctypes a :> rest) context where mock _ context = \_ -> mock (Proxy :: Proxy rest) context From 7fb9a95711aa6391faecb9ef3af195f4093b374e Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 26 May 2016 17:10:33 +0100 Subject: [PATCH 04/43] servant-client support for CaptureAll --- servant-client/src/Servant/Client.hs | 32 +++++++++++++++++++++++ servant-client/test/Servant/ClientSpec.hs | 9 ++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/servant-client/src/Servant/Client.hs b/servant-client/src/Servant/Client.hs index fd3ab24b..18581075 100644 --- a/servant-client/src/Servant/Client.hs +++ b/servant-client/src/Servant/Client.hs @@ -118,6 +118,38 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient api) where p = unpack (toUrlPiece val) +-- | If you use a 'CaptureAll' in one of your endpoints in your API, +-- the corresponding querying function will automatically take an +-- additional argument of a list of the type specified by your +-- 'CaptureAll'. That function will take care of inserting a textual +-- representation of this value at the right place in the request +-- path. +-- +-- You can control how these values are turned into text by specifying +-- a 'ToHttpApiData' instance of your type. +-- +-- Example: +-- +-- > type MyAPI = "src" :> CaptureAll Text -> Get '[JSON] SourceFile +-- > +-- > myApi :: Proxy +-- > myApi = Proxy +-- +-- > getSourceFile :: [Text] -> Manager -> BaseUrl -> ClientM SourceFile +-- > getSourceFile = client myApi +-- > -- then you can use "getSourceFile" to query that endpoint +instance (KnownSymbol capture, ToHttpApiData a, HasClient sublayout) + => HasClient (CaptureAll capture a :> sublayout) where + + type Client (CaptureAll capture a :> sublayout) = + [a] -> Client sublayout + + clientWithRoute Proxy req vals = + clientWithRoute (Proxy :: Proxy sublayout) + (foldl' (flip appendToPath) req ps) + + where ps = map (unpack . toUrlPiece) vals + instance OVERLAPPABLE_ -- Note [Non-Empty Content Types] (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts) diff --git a/servant-client/test/Servant/ClientSpec.hs b/servant-client/test/Servant/ClientSpec.hs index 4c21e201..0d2f36dc 100644 --- a/servant-client/test/Servant/ClientSpec.hs +++ b/servant-client/test/Servant/ClientSpec.hs @@ -105,6 +105,7 @@ type Api = "get" :> Get '[JSON] Person :<|> "deleteEmpty" :> DeleteNoContent '[JSON] NoContent :<|> "capture" :> Capture "name" String :> Get '[JSON,FormUrlEncoded] Person + :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] Person :<|> "body" :> ReqBody '[FormUrlEncoded,JSON] Person :> Post '[JSON] Person :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person] @@ -125,6 +126,7 @@ api = Proxy getGet :: C.Manager -> BaseUrl -> SCR.ClientM Person getDeleteEmpty :: C.Manager -> BaseUrl -> SCR.ClientM NoContent getCapture :: String -> C.Manager -> BaseUrl -> SCR.ClientM Person +getCaptureAll :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM Person getBody :: Person -> C.Manager -> BaseUrl -> SCR.ClientM Person getQueryParam :: Maybe String -> C.Manager -> BaseUrl -> SCR.ClientM Person getQueryParams :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM [Person] @@ -140,6 +142,7 @@ getDeleteContentType :: C.Manager -> BaseUrl -> SCR.ClientM NoContent getGet :<|> getDeleteEmpty :<|> getCapture + :<|> getCaptureAll :<|> getBody :<|> getQueryParam :<|> getQueryParams @@ -155,6 +158,7 @@ server = serve api ( return alice :<|> return NoContent :<|> (\ name -> return $ Person name 0) + :<|> (\ (name : _) -> return $ Person name 0) :<|> return :<|> (\ name -> case name of Just "alice" -> return alice @@ -250,6 +254,9 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do it "Servant.API.Capture" $ \(_, baseUrl) -> do (left show <$> runExceptT (getCapture "Paula" manager baseUrl)) `shouldReturn` Right (Person "Paula" 0) + it "Servant.API.CaptureAll" $ \(_, baseUrl) -> do + (left show <$> runExceptT (getCaptureAll ["Paula", "Peta"] manager baseUrl)) `shouldReturn` Right (Person "Paula" 0) + it "Servant.API.ReqBody" $ \(_, baseUrl) -> do let p = Person "Clara" 42 (left show <$> runExceptT (getBody p manager baseUrl)) `shouldReturn` Right p @@ -351,7 +358,7 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do _ -> fail $ "expected UnsupportedContentType, but got " <> show res it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do - let (_ :<|> _ :<|> _ :<|> getBody :<|> _) = client api + let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api Left res <- runExceptT (getBody alice manager baseUrl) case res of InvalidContentTypeHeader "fooooo" _ -> return () From c79a254b36a6c81f704a60d13aeb4e4e5f17da22 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 26 May 2016 19:10:15 +0100 Subject: [PATCH 05/43] servant-server support for CaptureAll --- servant-server/src/Servant/Server/Internal.hs | 47 +++++++++++++-- .../src/Servant/Server/Internal/Router.hs | 10 ++++ .../Server/Internal/RoutingApplication.hs | 5 +- servant-server/test/Servant/ServerSpec.hs | 57 ++++++++++++++++++- 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/servant-server/src/Servant/Server/Internal.hs b/servant-server/src/Servant/Server/Internal.hs index 21374dbe..de4a237a 100644 --- a/servant-server/src/Servant/Server/Internal.hs +++ b/servant-server/src/Servant/Server/Internal.hs @@ -45,13 +45,15 @@ import Prelude.Compat import Web.HttpApiData (FromHttpApiData) import Web.HttpApiData.Internal (parseHeaderMaybe, parseQueryParamMaybe, - parseUrlPieceMaybe) + parseUrlPieceMaybe, + parseUrlPieces) import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture, - Verb, ReflectMethod(reflectMethod), - IsSecure(..), Header, - QueryFlag, QueryParam, QueryParams, - Raw, RemoteHost, ReqBody, Vault, + CaptureAll, Verb, + ReflectMethod(reflectMethod), + IsSecure(..), Header, QueryFlag, + QueryParam, QueryParams, Raw, + RemoteHost, ReqBody, Vault, WithNamedContext) import Servant.API.ContentTypes (AcceptHeader (..), AllCTRender (..), @@ -128,11 +130,44 @@ instance (KnownSymbol capture, FromHttpApiData a, HasServer api context) CaptureRouter $ route (Proxy :: Proxy api) context - (addCapture d $ \ txt -> case parseUrlPieceMaybe txt :: Maybe a of + (addCapture d $ \ txt -> case parseUrlPieceMaybe txt of Nothing -> delayedFail err400 Just v -> return v ) +-- | If you use 'CaptureAll' in one of the endpoints for your API, +-- this automatically requires your server-side handler to be a +-- function that takes an argument of a list of the type specified by +-- the 'CaptureAll'. This lets servant worry about getting values from +-- the URL and turning them into values of the type you specify. +-- +-- You can control how they'll be converted from 'Text' to your type +-- by simply providing an instance of 'FromHttpApiData' for your type. +-- +-- Example: +-- +-- > type MyApi = "src" :> CaptureAll "segments" Text :> Get '[JSON] SourceFile +-- > +-- > server :: Server MyApi +-- > server = getSourceFile +-- > where getSourceFile :: [Text] -> Handler Book +-- > getSourceFile pathSegments = ... +instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout context) + => HasServer (CaptureAll capture a :> sublayout) context where + + type ServerT (CaptureAll capture a :> sublayout) m = + [a] -> ServerT sublayout m + + route Proxy context d = + CaptureAllRouter $ + route (Proxy :: Proxy sublayout) + context + (addCapture d $ \ txts -> case parseUrlPieces txts of + Left _ -> delayedFail err400 + Right v -> return v + ) + + allowedMethodHead :: Method -> Request -> Bool allowedMethodHead method request = method == methodGet && requestMethod request == methodHead diff --git a/servant-server/src/Servant/Server/Internal/Router.hs b/servant-server/src/Servant/Server/Internal/Router.hs index 3b69c04c..d01cc67a 100644 --- a/servant-server/src/Servant/Server/Internal/Router.hs +++ b/servant-server/src/Servant/Server/Internal/Router.hs @@ -31,6 +31,9 @@ data Router' env a = | CaptureRouter (Router' (Text, env) a) -- ^ first path component is passed to the child router in its -- environment and removed afterwards + | CaptureAllRouter (Router' ([Text], env) a) + -- ^ all path components are passed to the child router in its + -- environment and are removed afterwards | RawRouter (env -> a) -- ^ to be used for routes we do not know anything about | Choice (Router' env a) (Router' env a) @@ -90,6 +93,9 @@ routerStructure (StaticRouter m ls) = routerStructure (CaptureRouter router) = CaptureRouterStructure $ routerStructure router +routerStructure (CaptureAllRouter router) = + CaptureRouterStructure $ + routerStructure router routerStructure (RawRouter _) = RawRouterStructure routerStructure (Choice r1 r2) = @@ -163,6 +169,10 @@ runRouterEnv router env request respond = first : rest -> let request' = request { pathInfo = rest } in runRouterEnv router' (first, env) request' respond + CaptureAllRouter router' -> + let segments = pathInfo request + request' = request { pathInfo = [] } + in runRouterEnv router' (segments, env) request' respond RawRouter app -> app env request respond Choice r1 r2 -> diff --git a/servant-server/src/Servant/Server/Internal/RoutingApplication.hs b/servant-server/src/Servant/Server/Internal/RoutingApplication.hs index 10bdc461..5f78d0bb 100644 --- a/servant-server/src/Servant/Server/Internal/RoutingApplication.hs +++ b/servant-server/src/Servant/Server/Internal/RoutingApplication.hs @@ -11,7 +11,6 @@ module Servant.Server.Internal.RoutingApplication where import Control.Monad (ap, liftM) import Control.Monad.Trans (MonadIO(..)) import Control.Monad.Trans.Except (runExceptT) -import Data.Text (Text) import Network.Wai (Application, Request, Response, ResponseReceived) import Prelude () @@ -161,8 +160,8 @@ withRequest f = DelayedIO (\ req -> runDelayedIO (f req) req) -- | Add a capture to the end of the capture block. addCapture :: Delayed env (a -> b) - -> (Text -> DelayedIO a) - -> Delayed (Text, env) b + -> (captured -> DelayedIO a) + -> Delayed (captured, env) b addCapture Delayed{..} new = Delayed { capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt diff --git a/servant-server/test/Servant/ServerSpec.hs b/servant-server/test/Servant/ServerSpec.hs index 4e1adade..2337c258 100644 --- a/servant-server/test/Servant/ServerSpec.hs +++ b/servant-server/test/Servant/ServerSpec.hs @@ -39,7 +39,7 @@ import Network.Wai.Test (defaultRequest, request, simpleHeaders, simpleStatus) import Servant.API ((:<|>) (..), (:>), AuthProtect, BasicAuth, BasicAuthData(BasicAuthData), - Capture, Delete, Get, Header (..), + Capture, CaptureAll, Delete, Get, Header (..), Headers, HttpVersion, IsSecure (..), JSON, NoContent (..), Patch, PlainText, @@ -216,6 +216,58 @@ captureSpec = do it "strips the captured path snippet from pathInfo" $ do get "/captured/foo" `shouldRespondWith` (fromString (show ["foo" :: String])) +-- }}} +------------------------------------------------------------------------------ +-- * captureAllSpec {{{ +------------------------------------------------------------------------------ + +type CaptureAllApi = CaptureAll "legs" Integer :> Get '[JSON] Animal +captureAllApi :: Proxy CaptureAllApi +captureAllApi = Proxy +captureAllServer :: [Integer] -> Handler Animal +captureAllServer legs = case sum legs of + 4 -> return jerry + 2 -> return tweety + 0 -> return beholder + _ -> throwE err404 + +captureAllSpec :: Spec +captureAllSpec = do + describe "Servant.API.CaptureAll" $ do + with (return (serve captureAllApi captureAllServer)) $ do + + it "can capture a single element of the 'pathInfo'" $ do + response <- get "/2" + liftIO $ decode' (simpleBody response) `shouldBe` Just tweety + + it "can capture multiple elements of the 'pathInfo'" $ do + response <- get "/2/2" + liftIO $ decode' (simpleBody response) `shouldBe` Just jerry + + it "can capture arbitrarily many elements of the 'pathInfo'" $ do + response <- get "/1/1/0/1/0/1" + liftIO $ decode' (simpleBody response) `shouldBe` Just jerry + + it "can capture when there are no elements in 'pathInfo'" $ do + response <- get "/" + liftIO $ decode' (simpleBody response) `shouldBe` Just beholder + + it "returns 400 if the decoding fails" $ do + get "/notAnInt" `shouldRespondWith` 400 + + it "returns 400 if the decoding fails, regardless of which element" $ do + get "/1/0/0/notAnInt/3/" `shouldRespondWith` 400 + + it "returns 400 if the decoding fails, even when it's multiple elements" $ do + get "/1/0/0/notAnInt/3/orange/" `shouldRespondWith` 400 + + with (return (serve + (Proxy :: Proxy (CaptureAll "segments" String :> Raw)) + (\ _captured request_ respond -> + respond $ responseLBS ok200 [] (cs $ show $ pathInfo request_)))) $ do + it "consumes everything from pathInfo" $ do + get "/captured/foo/bar/baz" `shouldRespondWith` (fromString (show ([] :: [Int]))) + -- }}} ------------------------------------------------------------------------------ -- * queryParamSpec {{{ @@ -644,4 +696,7 @@ jerry = Animal "Mouse" 4 tweety :: Animal tweety = Animal "Bird" 2 + +beholder :: Animal +beholder = Animal "Beholder" 0 -- }}} From cda31614bfa6a65ad22d3f5f6bf0a9386d311bc5 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 26 May 2016 20:51:28 +0100 Subject: [PATCH 06/43] servant-foreign support for CaptureAll --- .../src/Servant/Foreign/Internal.hs | 15 +++++++++++++++ servant-foreign/test/Servant/ForeignSpec.hs | 18 ++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/servant-foreign/src/Servant/Foreign/Internal.hs b/servant-foreign/src/Servant/Foreign/Internal.hs index 0e68cc6c..59d09436 100644 --- a/servant-foreign/src/Servant/Foreign/Internal.hs +++ b/servant-foreign/src/Servant/Foreign/Internal.hs @@ -211,6 +211,21 @@ instance (KnownSymbol sym, HasForeignType lang ftype t, HasForeign lang ftype ap { _argName = PathSegment str , _argType = ftype } +instance (KnownSymbol sym, HasForeignType lang ftype [t], HasForeign lang ftype sublayout) + => HasForeign lang ftype (CaptureAll sym t :> sublayout) where + type Foreign ftype (CaptureAll sym t :> sublayout) = Foreign ftype sublayout + + foreignFor lang Proxy Proxy req = + foreignFor lang Proxy (Proxy :: Proxy sublayout) $ + req & reqUrl . path <>~ [Segment (Cap arg)] + & reqFuncName . _FunctionName %~ (++ ["by", str]) + where + str = pack . symbolVal $ (Proxy :: Proxy sym) + ftype = typeFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy [t]) + arg = Arg + { _argName = PathSegment str + , _argType = ftype } + instance (Elem JSON list, HasForeignType lang ftype a, ReflectMethod method) => HasForeign lang ftype (Verb method status list a) where type Foreign ftype (Verb method status list a) = Req ftype diff --git a/servant-foreign/test/Servant/ForeignSpec.hs b/servant-foreign/test/Servant/ForeignSpec.hs index 2df0c1ba..966861d5 100644 --- a/servant-foreign/test/Servant/ForeignSpec.hs +++ b/servant-foreign/test/Servant/ForeignSpec.hs @@ -46,6 +46,7 @@ type TestApi :<|> "test" :> QueryParam "param" Int :> ReqBody '[JSON] [String] :> Post '[JSON] NoContent :<|> "test" :> QueryParams "params" Int :> ReqBody '[JSON] String :> Put '[JSON] NoContent :<|> "test" :> Capture "id" Int :> Delete '[JSON] NoContent + :<|> "test" :> CaptureAll "ids" Int :> Get '[JSON] [Int] testApi :: [Req String] testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy String) (Proxy :: Proxy TestApi) @@ -53,9 +54,9 @@ testApi = listFromAPI (Proxy :: Proxy LangX) (Proxy :: Proxy String) (Proxy :: P listFromAPISpec :: Spec listFromAPISpec = describe "listFromAPI" $ do it "generates 4 endpoints for TestApi" $ do - length testApi `shouldBe` 4 + length testApi `shouldBe` 5 - let [getReq, postReq, putReq, deleteReq] = testApi + let [getReq, postReq, putReq, deleteReq, captureAllReq] = testApi it "collects all info for get request" $ do shouldBe getReq $ defReq @@ -106,3 +107,16 @@ listFromAPISpec = describe "listFromAPI" $ do , _reqReturnType = Just "voidX" , _reqFuncName = FunctionName ["delete", "test", "by", "id"] } + + it "collects all info for capture all request" $ do + shouldBe captureAllReq $ defReq + { _reqUrl = Url + [ Segment $ Static "test" + , Segment $ Cap (Arg "ids" "listX of intX") ] + [] + , _reqMethod = "GET" + , _reqHeaders = [] + , _reqBody = Nothing + , _reqReturnType = Just "listX of intX" + , _reqFuncName = FunctionName ["get", "test", "by", "ids"] + } From ed702f59273da12316c2ee3a8deba65b31e09385 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 26 May 2016 21:22:11 +0100 Subject: [PATCH 07/43] servant-docs support for CaptureAll --- servant-docs/src/Servant/Docs/Internal.hs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index fad8717c..7b181822 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -702,6 +702,22 @@ instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs api) symP = Proxy :: Proxy sym +-- | @"books" :> 'CaptureAll' "isbn" Text@ will appear as +-- @/books/:isbn@ in the docs. +instance (KnownSymbol sym, ToCapture (CaptureAll sym a), HasDocs sublayout) + => HasDocs (CaptureAll sym a :> sublayout) where + + docsFor Proxy (endpoint, action) = + docsFor sublayoutP (endpoint', action') + + where sublayoutP = Proxy :: Proxy sublayout + captureP = Proxy :: Proxy (CaptureAll sym a) + + action' = over captures (|> toCapture captureP) action + endpoint' = over path (\p -> p ++ [":" ++ symbolVal symP]) endpoint + symP = Proxy :: Proxy sym + + instance OVERLAPPABLE_ (ToSample a, AllMimeRender (ct ': cts) a, KnownNat status , ReflectMethod method) From d945c99f822d639db09d70db2dea4321fd406f95 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Tue, 7 Jun 2016 09:50:46 +0100 Subject: [PATCH 08/43] Test whole list in CaptureAll client tests --- servant-client/test/Servant/ClientSpec.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/servant-client/test/Servant/ClientSpec.hs b/servant-client/test/Servant/ClientSpec.hs index 0d2f36dc..da7c763b 100644 --- a/servant-client/test/Servant/ClientSpec.hs +++ b/servant-client/test/Servant/ClientSpec.hs @@ -105,7 +105,7 @@ type Api = "get" :> Get '[JSON] Person :<|> "deleteEmpty" :> DeleteNoContent '[JSON] NoContent :<|> "capture" :> Capture "name" String :> Get '[JSON,FormUrlEncoded] Person - :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] Person + :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] [Person] :<|> "body" :> ReqBody '[FormUrlEncoded,JSON] Person :> Post '[JSON] Person :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person] @@ -126,7 +126,7 @@ api = Proxy getGet :: C.Manager -> BaseUrl -> SCR.ClientM Person getDeleteEmpty :: C.Manager -> BaseUrl -> SCR.ClientM NoContent getCapture :: String -> C.Manager -> BaseUrl -> SCR.ClientM Person -getCaptureAll :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM Person +getCaptureAll :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM [Person] getBody :: Person -> C.Manager -> BaseUrl -> SCR.ClientM Person getQueryParam :: Maybe String -> C.Manager -> BaseUrl -> SCR.ClientM Person getQueryParams :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM [Person] @@ -158,7 +158,7 @@ server = serve api ( return alice :<|> return NoContent :<|> (\ name -> return $ Person name 0) - :<|> (\ (name : _) -> return $ Person name 0) + :<|> (\ names -> return (zipWith Person names [0..])) :<|> return :<|> (\ name -> case name of Just "alice" -> return alice @@ -255,7 +255,8 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do (left show <$> runExceptT (getCapture "Paula" manager baseUrl)) `shouldReturn` Right (Person "Paula" 0) it "Servant.API.CaptureAll" $ \(_, baseUrl) -> do - (left show <$> runExceptT (getCaptureAll ["Paula", "Peta"] manager baseUrl)) `shouldReturn` Right (Person "Paula" 0) + let expected = [(Person "Paula" 0), (Person "Peta" 1)] + (left show <$> runExceptT (getCaptureAll ["Paula", "Peta"] manager baseUrl)) `shouldReturn` Right expected it "Servant.API.ReqBody" $ \(_, baseUrl) -> do let p = Person "Clara" 42 From b714640110519092e513c8d7eb4885a13a85caba Mon Sep 17 00:00:00 2001 From: Arian van Putten Date: Mon, 4 Jul 2016 14:38:00 +0200 Subject: [PATCH 09/43] Add CaptureAll to ComprehensiveAPI --- servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs b/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs index 5666ba0c..f568e56f 100644 --- a/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs +++ b/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs @@ -30,7 +30,8 @@ type ComprehensiveAPI = Vault :> GET :<|> Verb 'POST 204 '[JSON] NoContent :<|> Verb 'POST 204 '[JSON] Int :<|> - WithNamedContext "foo" '[] GET + WithNamedContext "foo" '[] GET :<|> + CaptureAll "foo" Int :> GET comprehensiveAPI :: Proxy ComprehensiveAPI comprehensiveAPI = Proxy From c5dcff12d020fab3489777074aeec825e269c9d3 Mon Sep 17 00:00:00 2001 From: Arian van Putten Date: Mon, 4 Jul 2016 14:47:34 +0200 Subject: [PATCH 10/43] Add dummy docs instancE --- servant-docs/test/Servant/DocsSpec.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/servant-docs/test/Servant/DocsSpec.hs b/servant-docs/test/Servant/DocsSpec.hs index 018fd46b..054ea00a 100644 --- a/servant-docs/test/Servant/DocsSpec.hs +++ b/servant-docs/test/Servant/DocsSpec.hs @@ -34,6 +34,8 @@ instance ToParam (QueryFlag "foo") where toParam = error "unused" instance ToCapture (Capture "foo" Int) where toCapture = error "unused" +instance ToCapture (CaptureAll "foo" Int) where + toCapture = error "unused" -- * specs From ecfa78d2228a0d52381bf7eb4ebe2673840c96fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Fri, 15 Jul 2016 19:31:16 +0200 Subject: [PATCH 11/43] doc: add a tip about how to serve on `/` --- doc/tutorial/ApiType.lhs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/tutorial/ApiType.lhs b/doc/tutorial/ApiType.lhs index 53aa187f..096903e8 100644 --- a/doc/tutorial/ApiType.lhs +++ b/doc/tutorial/ApiType.lhs @@ -58,6 +58,14 @@ Let's break that down: is equivalent to `/list-all/users`. This means that sometimes `:>` is somehow equivalent to `/`, but sometimes it just lets you chain another combinator. +Tip: If your endpoint responds to `/` (the root path), just omit any combinators +that introduce path segments. E.g. the following api has only one endpoint on `/`: + +``` haskell +type RootEndpoint = + Get '[JSON] User +``` + We can also describe APIs with multiple endpoints by using the `:<|>` combinators. Here's an example: From 197ed0548a0e6a5b6fcf7f9926b7a041ffd4b97f Mon Sep 17 00:00:00 2001 From: Oleg Grenrus Date: Sun, 10 Jul 2016 23:28:51 +0300 Subject: [PATCH 12/43] Use parseRequest --- servant-client/src/Servant/Common/Req.hs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/servant-client/src/Servant/Common/Req.hs b/servant-client/src/Servant/Common/Req.hs index 6d922634..eb921316 100644 --- a/servant-client/src/Servant/Common/Req.hs +++ b/servant-client/src/Servant/Common/Req.hs @@ -103,7 +103,7 @@ setRQBody b t req = req { reqBody = Just (b, t) } reqToRequest :: (Functor m, MonadThrow m) => Req -> BaseUrl -> m Request reqToRequest req (BaseUrl reqScheme reqHost reqPort path) = - setheaders . setAccept . setrqb . setQS <$> parseUrlThrow url + setheaders . setAccept . setrqb . setQS <$> parseRequest url where url = show $ nullURI { uriScheme = case reqScheme of Http -> "http:" @@ -129,8 +129,18 @@ reqToRequest req (BaseUrl reqScheme reqHost reqPort path) = | not . null . reqAccept $ req] } toProperHeader (name, val) = (fromString name, encodeUtf8 val) + #if !MIN_VERSION_http_client(0,4,30) - parseUrlThrow = parseUrl +-- 'parseRequest' is introduced in http-client-0.4.30 +-- it differs from 'parseUrl', by not throwing exceptions on non-2xx http statuses +-- +-- See for implementations: +-- http://hackage.haskell.org/package/http-client-0.4.30/docs/src/Network-HTTP-Client-Request.html#parseRequest +-- http://hackage.haskell.org/package/http-client-0.5.0/docs/src/Network-HTTP-Client-Request.html#parseRequest +parseRequest :: MonadThrow m => String -> m Request +parseRequest url = disableStatusCheck <$> parseUrl url + where + disableStatusCheck req = req { checkStatus = \ _status _headers _cookies -> Nothing } #endif @@ -147,8 +157,7 @@ performRequest :: Method -> Req -> Manager -> BaseUrl performRequest reqMethod req manager reqHost = do partialRequest <- liftIO $ reqToRequest req reqHost - let request = disableStatusCheck $ - partialRequest { Client.method = reqMethod } + let request = partialRequest { Client.method = reqMethod } eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request manager case eResponse of @@ -169,13 +178,6 @@ performRequest reqMethod req manager reqHost = do throwE $ FailureResponse status ct body return (status_code, body, ct, hdrs, response) -disableStatusCheck :: Request -> Request -#if MIN_VERSION_http_client(0,5,0) -disableStatusCheck req = req { checkResponse = \ _req _res -> return () } -#else -disableStatusCheck req = req { checkStatus = \ _status _headers _cookies -> Nothing } -#endif - performRequestCT :: MimeUnrender ct result => Proxy ct -> Method -> Req -> Manager -> BaseUrl -> ClientM ([HTTP.Header], result) From 6999b002977fa1a726ea627f716e47c677047713 Mon Sep 17 00:00:00 2001 From: Oleg Grenrus Date: Sun, 7 Aug 2016 20:12:09 +0300 Subject: [PATCH 13/43] Support aeson-1 --- servant-client/servant-client.cabal | 2 +- servant-js/servant-js.cabal | 2 +- servant-server/servant-server.cabal | 2 +- servant/servant.cabal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index 2dfc4403..089e1209 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -36,7 +36,7 @@ library Servant.Common.Req build-depends: base >= 4.7 && < 4.10 - , aeson >= 0.7 && < 0.12 + , aeson >= 0.7 && < 1.1 , attoparsec >= 0.12 && < 0.14 , base64-bytestring >= 1.0.0.1 && < 1.1 , bytestring >= 0.10 && < 0.11 diff --git a/servant-js/servant-js.cabal b/servant-js/servant-js.cabal index d29dc96c..726e0b4e 100644 --- a/servant-js/servant-js.cabal +++ b/servant-js/servant-js.cabal @@ -65,7 +65,7 @@ executable counter buildable: False build-depends: base >= 4.7 && < 5 - , aeson >= 0.7 && < 0.12 + , aeson >= 0.7 && < 1.1 , filepath >= 1 , lens >= 4 , servant == 0.8.* diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index 66a18577..ff84d947 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -47,7 +47,7 @@ library build-depends: base >= 4.7 && < 4.10 , base-compat >= 0.9 && < 0.10 - , aeson >= 0.7 && < 0.12 + , aeson >= 0.7 && < 1.1 , attoparsec >= 0.12 && < 0.14 , base64-bytestring >= 1.0 && < 1.1 , bytestring >= 0.10 && < 0.11 diff --git a/servant/servant.cabal b/servant/servant.cabal index 48784cac..3c89171f 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -51,7 +51,7 @@ library build-depends: base >= 4.7 && < 4.10 , base-compat >= 0.9 && < 0.10 - , aeson >= 0.7 && < 0.12 + , aeson >= 0.7 && < 1.1 , attoparsec >= 0.12 && < 0.14 , bytestring >= 0.10 && < 0.11 , bytestring-conversion >= 0.3 && < 0.4 From 490502cad1fab8ea7c1ff96f68fec5c355fee0ba Mon Sep 17 00:00:00 2001 From: Oleg Grenrus Date: Wed, 10 Aug 2016 17:49:56 +0300 Subject: [PATCH 14/43] Support QuickCheck-2.9 --- servant-mock/servant-mock.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servant-mock/servant-mock.cabal b/servant-mock/servant-mock.cabal index f71dbd75..89ff1f6f 100644 --- a/servant-mock/servant-mock.cabal +++ b/servant-mock/servant-mock.cabal @@ -34,7 +34,7 @@ library servant == 0.8.*, servant-server == 0.8.*, transformers >= 0.3 && <0.6, - QuickCheck >= 2.7 && <2.9, + QuickCheck >= 2.7 && <2.10, wai >= 3.0 && <3.3 hs-source-dirs: src default-language: Haskell2010 From b1f143010e939ef6b180d636bfd14050eb39f945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Sun, 14 Aug 2016 20:13:46 +0200 Subject: [PATCH 15/43] servant-client: fix compilation when building with http-client < 0.4.30 --- servant-client/src/Servant/Common/Req.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servant-client/src/Servant/Common/Req.hs b/servant-client/src/Servant/Common/Req.hs index eb921316..ea610cce 100644 --- a/servant-client/src/Servant/Common/Req.hs +++ b/servant-client/src/Servant/Common/Req.hs @@ -138,7 +138,7 @@ reqToRequest req (BaseUrl reqScheme reqHost reqPort path) = -- http://hackage.haskell.org/package/http-client-0.4.30/docs/src/Network-HTTP-Client-Request.html#parseRequest -- http://hackage.haskell.org/package/http-client-0.5.0/docs/src/Network-HTTP-Client-Request.html#parseRequest parseRequest :: MonadThrow m => String -> m Request -parseRequest url = disableStatusCheck <$> parseUrl url +parseRequest url = liftM disableStatusCheck (parseUrl url) where disableStatusCheck req = req { checkStatus = \ _status _headers _cookies -> Nothing } #endif From d0a28e46b032ee8f6de707d99379ad04e117c607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Sun, 14 Aug 2016 19:58:05 +0200 Subject: [PATCH 16/43] add missing test file to servant-server.cabal --- servant-server/servant-server.cabal | 1 + 1 file changed, 1 insertion(+) diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index ff84d947..d055df2f 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -99,6 +99,7 @@ test-suite spec hs-source-dirs: test main-is: Spec.hs other-modules: + Servant.ArbitraryMonadServerSpec Servant.Server.ErrorSpec Servant.Server.Internal.ContextSpec Servant.Server.RouterSpec From 4ccfce2331c4b89f051ec6592b5bead975353bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Sun, 14 Aug 2016 22:45:32 +0200 Subject: [PATCH 17/43] docs: bump Sphinx version --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 0c9c95a8..93ddeccc 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -17,7 +17,7 @@ recommonmark==0.4.0 singledispatch==3.4.0.3 six==1.10.0 snowballstemmer==1.2.1 -Sphinx==1.3.4 +Sphinx==1.3.6 sphinx-autobuild==0.5.2 sphinx-rtd-theme==0.1.9 tornado==4.3 From 1b83d30cf3ea89a8ad7b52c0640fa91b095cc214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Thu, 11 Aug 2016 16:53:46 +0200 Subject: [PATCH 18/43] switch ci to stack and add ci cron job to test hackage breakage --- .travis.yml | 28 +++++++++++----------------- travis.sh => scripts/ci-cron.sh | 9 ++++++++- 2 files changed, 19 insertions(+), 18 deletions(-) rename travis.sh => scripts/ci-cron.sh (56%) diff --git a/.travis.yml b/.travis.yml index d6854b8a..1a006a6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,33 +3,27 @@ sudo: false language: c env: - - GHCVER=7.8.4 CABALVER=1.22 - - GHCVER=7.10.3 CABALVER=1.22 - - GHCVER=8.0.1 CABALVER=1.24 + - STACK_YAML=stack-ghc-7.8.4.yaml + - STACK_YAML=stack.yaml + - STACK_YAML=stack-ghc-8.0.1.yaml addons: apt: - sources: - - hvr-ghc packages: - - ghc-7.8.4 - - ghc-7.10.3 - - ghc-8.0.1 - - cabal-install-1.22 - - cabal-install-1.24 - libgmp-dev install: - - (mkdir -p $HOME/.local/bin && cd $HOME/.local/bin && wget https://zalora-public.s3.amazonaws.com/tinc && chmod +x tinc) - - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH - - ghc --version - - cabal --version - - travis_retry cabal update - - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config + - mkdir -p ~/.local/bin + - export PATH=$HOME/.local/bin:$PATH + - travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' + - stack --version + - stack setup --no-terminal + - (cd $HOME/.local/bin && wget https://zalora-public.s3.amazonaws.com/tinc && chmod +x tinc) script: - - ./travis.sh + - if [ "$TRAVIS_EVENT_TYPE" = "cron" ] ; then ./scripts/ci-cron.sh ; else stack test --ghc-options=-Werror --no-terminal ; fi cache: directories: - $HOME/.tinc/cache + - $HOME/.stack diff --git a/travis.sh b/scripts/ci-cron.sh similarity index 56% rename from travis.sh rename to scripts/ci-cron.sh index cd815efb..27be3e35 100755 --- a/travis.sh +++ b/scripts/ci-cron.sh @@ -1,8 +1,15 @@ #!/usr/bin/env bash +set -o nounset set -o errexit +set -o verbose -for package in $(cat sources.txt) doc/tutorial ; do +export PATH=$(stack path --bin-path):$PATH + +stack install cabal-install +cabal update + +for package in $(cat sources.txt) ; do echo testing $package pushd $package tinc From f5efaf9416bf619e4c082cee07546335127616fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Wed, 17 Aug 2016 13:37:53 +0200 Subject: [PATCH 19/43] add changelog entry for `CaptureAll` --- servant/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index dd3c33ca..f35679d1 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -1,3 +1,8 @@ +next +---- + +* Add `CaptureAll` combinator. Captures all of the remaining segments in a URL. + 0.8 --- From 95d077f5867fe70f253a54640ef03ab8a6c131dd Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Tue, 10 May 2016 17:44:01 +0200 Subject: [PATCH 20/43] Add more JS documentation. Contributed by freezeboy in PR #11 - https://github.com/haskell-servant/haskell-servant.github.io/pull/11/files --- doc/tutorial/Javascript.lhs | 355 +++++++++++++++++++++++++++++++----- 1 file changed, 313 insertions(+), 42 deletions(-) diff --git a/doc/tutorial/Javascript.lhs b/doc/tutorial/Javascript.lhs index 4054e4b3..921fe40f 100644 --- a/doc/tutorial/Javascript.lhs +++ b/doc/tutorial/Javascript.lhs @@ -151,57 +151,36 @@ so we need a `Proxy` for our API type `API'` without its `Raw` endpoint. Very similarly to how one can derive haskell functions, we can derive the javascript with just a simple function call to `jsForAPI` from -`Servant.JQuery`. +`Servant.JS`. ``` haskell apiJS :: Text -apiJS = jsForAPI api vanillaJS +apiJS = jsForAPI api jquery ``` This `Text` contains 2 Javascript functions, 'getPoint' and 'getBooks': ``` javascript -var getPoint = function(onSuccess, onError) -{ - var xhr = new XMLHttpRequest(); - xhr.open('GET', '/point', true); - xhr.setRequestHeader("Accept","application/json"); - xhr.onreadystatechange = function (e) { - if (xhr.readyState == 4) { - if (xhr.status == 204 || xhr.status == 205) { - onSuccess(); - } else if (xhr.status >= 200 && xhr.status < 300) { - var value = JSON.parse(xhr.responseText); - onSuccess(value); - } else { - var value = JSON.parse(xhr.responseText); - onError(value); - } - } - } - xhr.send(null); -} +var getPoint = function (onSuccess, onError) + { + $.ajax( + { url: '/point' + , success: onSuccess + , error: onError + , method: 'GET' + }); +}; -var getBooks = function(q, onSuccess, onError) -{ - var xhr = new XMLHttpRequest(); - xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true); - xhr.setRequestHeader("Accept","application/json"); - xhr.onreadystatechange = function (e) { - if (xhr.readyState == 4) { - if (xhr.status == 204 || xhr.status == 205) { - onSuccess(); - } else if (xhr.status >= 200 && xhr.status < 300) { - var value = JSON.parse(xhr.responseText); - onSuccess(value); - } else { - var value = JSON.parse(xhr.responseText); - onError(value); - } - } - } - xhr.send(null); -} + +var getBooks = function (q, onSuccess, onError) + { + $.ajax( + { url: '/books' + '?q=' + encodeURIComponent(q) + , success: onSuccess + , error: onError + , method: 'GET' + }); +}; ``` We created a directory `static` that contains two static files: `index.html`, @@ -226,3 +205,295 @@ And we're good to go. You can start the `main` function of this file and go to `http://localhost:8000/`. Start typing in the name of one of the authors in our database or part of a book title, and check out how long it takes to approximate pi using the method mentioned above. + +## Customizations + +Instead of calling `jquery`, you can call its variant `jqueryWith`. +Here are the type definitions + +```haskell ignore +jquery :: JavaScriptGenerator +jqueryWith :: CommonGeneratorOptions -> JavaScriptGenerator +``` + +The `CommonGeneratorOptions` will let you define different behaviors to +change how functions are generated. Here is the definition of currently +available options: + +```haskell +data CommonGeneratorOptions = CommonGeneratorOptions + { + -- | function generating function names + functionNameBuilder :: FunctionName -> String + -- | name used when a user want to send the request body (to let you redefine it) + , requestBody :: String + -- | name of the callback parameter when the request was successful + , successCallback :: String + -- | name of the callback parameter when the request reported an error + , errorCallback :: String + -- | namespace on which we define the js function (empty mean local var) + , moduleName :: String + -- | a prefix that should be prepended to the URL in the generated JS + , urlPrefix :: String + } +``` + +This pattern is available with all supported backends, and default values are provided. + +## Vanilla support + +If you don't use JQuery for your application, you can reduce your +dependencies to simply use the `XMLHttpRequest` object from the standard API. + +Use the same code as before but simply replace the previous `apiJS` with +the following one: + +``` haskell +apiJS :: String +apiJS = jsForAPI api vanillaJS +``` + +The rest is *completely* unchanged. + +The output file is a bit different, but it has the same parameters, + +``` javascript + +var getPoint = function (onSuccess, onError) +{ + var xhr = new XMLHttpRequest(); + xhr.open('GET', '/point', true); + + xhr.onreadystatechange = function (e) { + if (xhr.readyState == 4) { + var value = JSON.parse(xhr.responseText); + if (xhr.status == 200 || xhr.status == 201) { + onSuccess(value); + } else { + onError(value); + } + } + } + xhr.send(null); +}; + +var getBooks = function (q, onSuccess, onError) +{ + var xhr = new XMLHttpRequest(); + xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true); + + xhr.onreadystatechange = function (e) { + if (xhr.readyState == 4) { + var value = JSON.parse(xhr.responseText); + if (xhr.status == 200 || xhr.status == 201) { + onSuccess(value); + } else { + onError(value); + } + } + } + xhr.send(null); +}; +``` + +And that's all, your web service can of course be accessible from those +two clients at the same time! + +## Axios support + +### Simple usage + +If you use Axios library for your application, we support that too! + +Use the same code as before but simply replace the previous `apiJS` with +the following one: + +``` haskell +apiJS :: String +apiJS = jsForAPI api $ axios defAxiosOptions +``` + +The rest is *completely* unchanged. + +The output file is a bit different, + +``` javascript + +var getPoint = function () +{ + return axios({ url: '/point' + , method: 'get' + }); +}; + +var getBooks = function (q) +{ + return axios({ url: '/books' + '?q=' + encodeURIComponent(q) + , method: 'get' + }); +}; +``` + +**Caution:** In order to support the promise style of the API, there are no onSuccess +nor onError callback functions. + +### Defining Axios configuration + +Axios lets you define a 'configuration' to determine the behavior of the +program when the AJAX request is sent. + +We mapped this into a configuration + +``` haskell +data AxiosOptions = AxiosOptions + { -- | indicates whether or not cross-site Access-Control requests + -- should be made using credentials + withCredentials :: !Bool + -- | the name of the cookie to use as a value for xsrf token + , xsrfCookieName :: !(Maybe String) + -- | the name of the header to use as a value for xsrf token + , xsrfHeaderName :: !(Maybe String) + } +``` + +## Angular support + +### Simple usage + +You can apply the same procedure as with `vanillaJS` and `jquery`, and +generate top level functions. + +The difference is that `angular` Generator always takes an argument. + +``` haskell +apiJS :: String +apiJS = jsForAPI api $ angular defAngularOptions +``` + +The generated code will be a bit different than previous generators. An extra +argument `$http` will be added to let Angular magical Dependency Injector +operate. + +**Caution:** In order to support the promise style of the API, there are no onSuccess +nor onError callback functions. + +``` javascript + +var getPoint = function($http) +{ + return $http( + { url: '/counter' + , method: 'GET' + }); +} + +var getBooks = function($http, q) +{ + return $http( + { url: '/books' + '?q=' + encodeURIComponent(q), true); + , method: 'GET' + }); +} +``` + +You can then build your controllers easily + +``` javascript + +app.controller("MyController", function($http) { + this.getPoint = getPoint($http) + .success(/* Do something */) + .error(/* Report error */); + + this.getPoint = getBooks($http, q) + .success(/* Do something */) + .error(/* Report error */); +}); +``` + +### Service generator + +You can also generate automatically a service to wrap the whole API as +a single Angular service: + +``` javascript +app.service('MyService', function($http) { + return ({ + postCounter: function() + { + return $http( + { url: '/counter' + , method: 'POST' + }); + }, + getCounter: function() + { + return $http( + { url: '/books' + '?q=' + encodeURIComponent(q), true); + , method: 'GET' + }); + } + }); +}); +``` + +To do so, you just have to use an alternate generator. + +``` haskell +apiJS :: String +apiJS = jsForAPI api $ angularService defAngularOptions +``` + +Again, it is possible to customize some portions with the options. + +``` haskell +data AngularOptions = AngularOptions + { -- | When generating code with wrapInService, name of the service to generate, default is 'app' + serviceName :: String + , -- | beginning of the service definition + prologue :: String -> String -> String + , -- | end of the service definition + epilogue :: String + } +``` + +# Custom function name builder + +Servant comes with three name builders included: + +- camelCase (the default) +- concatCase +- snakeCase + +Keeping the JQuery as an example, let's see the impact: + +``` haskell +apiJS :: String +apiJS = jsForAPI api $ jqueryWith defCommonGeneratorOptions { functionNameBuilder: snakeCase } +``` + +This `String` contains 2 Javascript functions: + +``` javascript + +var getPoint = function (onSuccess, onError) +{ + $.ajax( + { url: '/point' + , success: onSuccess + , error: onError + , method: 'GET' + }); +}; + +var getBooks = function (q, onSuccess, onError) +{ + $.ajax( + { url: '/books' + '?q=' + encodeURIComponent(q) + , success: onSuccess + , error: onError + , method: 'GET' + }); +}; +``` + From 9e71fde7562a6c1b7edf689cb6cc8a7773e28b1e Mon Sep 17 00:00:00 2001 From: freezeboy Date: Tue, 10 May 2016 23:04:19 +0200 Subject: [PATCH 21/43] Fixing typo in Javascript.lhs --- doc/tutorial/Javascript.lhs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorial/Javascript.lhs b/doc/tutorial/Javascript.lhs index 921fe40f..09c21df3 100644 --- a/doc/tutorial/Javascript.lhs +++ b/doc/tutorial/Javascript.lhs @@ -469,7 +469,7 @@ Keeping the JQuery as an example, let's see the impact: ``` haskell apiJS :: String -apiJS = jsForAPI api $ jqueryWith defCommonGeneratorOptions { functionNameBuilder: snakeCase } +apiJS = jsForAPI api $ jqueryWith defCommonGeneratorOptions { functionNameBuilder= snakeCase } ``` This `String` contains 2 Javascript functions: From 7aa550aa051d5868a09a2561ae33727409b3146b Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Thu, 12 May 2016 13:20:02 +0200 Subject: [PATCH 22/43] Make Javascript tutorial compile. --- doc/tutorial/Javascript.lhs | 179 +++++++++++++++------------- doc/tutorial/test/JavascriptSpec.hs | 7 +- servant-js/src/Servant/JS.hs | 1 + 3 files changed, 105 insertions(+), 82 deletions(-) diff --git a/doc/tutorial/Javascript.lhs b/doc/tutorial/Javascript.lhs index 09c21df3..4bc1466e 100644 --- a/doc/tutorial/Javascript.lhs +++ b/doc/tutorial/Javascript.lhs @@ -154,33 +154,33 @@ javascript with just a simple function call to `jsForAPI` from `Servant.JS`. ``` haskell -apiJS :: Text -apiJS = jsForAPI api jquery +apiJS1 :: Text +apiJS1 = jsForAPI api jquery ``` This `Text` contains 2 Javascript functions, 'getPoint' and 'getBooks': ``` javascript -var getPoint = function (onSuccess, onError) - { - $.ajax( - { url: '/point' - , success: onSuccess - , error: onError - , method: 'GET' - }); -}; +var getPoint = function(onSuccess, onError) +{ + $.ajax( + { url: '/point' + , success: onSuccess + , error: onError + , type: 'GET' + }); +} -var getBooks = function (q, onSuccess, onError) - { - $.ajax( - { url: '/books' + '?q=' + encodeURIComponent(q) - , success: onSuccess - , error: onError - , method: 'GET' - }); -}; +var getBooks = function(q, onSuccess, onError) +{ + $.ajax( + { url: '/books' + '?q=' + encodeURIComponent(q) + , success: onSuccess + , error: onError + , type: 'GET' + }); +} ``` We created a directory `static` that contains two static files: `index.html`, @@ -192,7 +192,7 @@ write the generated javascript into a file: ``` haskell writeJSFiles :: IO () writeJSFiles = do - T.writeFile "static/api.js" apiJS + T.writeFile "static/api.js" apiJS1 jq <- T.readFile =<< Language.Javascript.JQuery.file T.writeFile "static/jq.js" jq ``` @@ -220,7 +220,7 @@ The `CommonGeneratorOptions` will let you define different behaviors to change how functions are generated. Here is the definition of currently available options: -```haskell +```haskell ignore data CommonGeneratorOptions = CommonGeneratorOptions { -- | function generating function names @@ -249,8 +249,8 @@ Use the same code as before but simply replace the previous `apiJS` with the following one: ``` haskell -apiJS :: String -apiJS = jsForAPI api vanillaJS +apiJS2 :: Text +apiJS2 = jsForAPI api vanillaJS ``` The rest is *completely* unchanged. @@ -259,41 +259,50 @@ The output file is a bit different, but it has the same parameters, ``` javascript -var getPoint = function (onSuccess, onError) + +var getPoint = function(onSuccess, onError) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', '/point', true); - - xhr.onreadystatechange = function (e) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', '/point', true); + xhr.setRequestHeader(\"Accept\",\"application/json\"); + xhr.onreadystatechange = function (e) { if (xhr.readyState == 4) { + if (xhr.status == 204 || xhr.status == 205) { + onSuccess(); + } else if (xhr.status >= 200 && xhr.status < 300) { var value = JSON.parse(xhr.responseText); - if (xhr.status == 200 || xhr.status == 201) { - onSuccess(value); - } else { - onError(value); - } - } + onSuccess(value); + } else { + var value = JSON.parse(xhr.responseText); + onError(value); + } } - xhr.send(null); -}; + } + xhr.send(null); +} -var getBooks = function (q, onSuccess, onError) +var getBooks = function(q, onSuccess, onError) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true); - - xhr.onreadystatechange = function (e) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true); + xhr.setRequestHeader(\"Accept\",\"application/json\"); + xhr.onreadystatechange = function (e) { if (xhr.readyState == 4) { + if (xhr.status == 204 || xhr.status == 205) { + onSuccess(); + } else if (xhr.status >= 200 && xhr.status < 300) { var value = JSON.parse(xhr.responseText); - if (xhr.status == 200 || xhr.status == 201) { - onSuccess(value); - } else { - onError(value); - } - } + onSuccess(value); + } else { + var value = JSON.parse(xhr.responseText); + onError(value); + } } - xhr.send(null); -}; + } + xhr.send(null); +} + + ``` And that's all, your web service can of course be accessible from those @@ -309,8 +318,8 @@ Use the same code as before but simply replace the previous `apiJS` with the following one: ``` haskell -apiJS :: String -apiJS = jsForAPI api $ axios defAxiosOptions +apiJS3 :: Text +apiJS3 = jsForAPI api $ axios defAxiosOptions ``` The rest is *completely* unchanged. @@ -319,19 +328,23 @@ The output file is a bit different, ``` javascript -var getPoint = function () -{ - return axios({ url: '/point' - , method: 'get' - }); -}; -var getBooks = function (q) +var getPoint = function() { - return axios({ url: '/books' + '?q=' + encodeURIComponent(q) - , method: 'get' - }); -}; + return axios({ url: '/point' + , method: 'get' + }); +} + + + +var getBooks = function(q) +{ + return axios({ url: '/books' + '?q=' + encodeURIComponent(q) + , method: 'get' + }); +} + ``` **Caution:** In order to support the promise style of the API, there are no onSuccess @@ -366,8 +379,8 @@ generate top level functions. The difference is that `angular` Generator always takes an argument. ``` haskell -apiJS :: String -apiJS = jsForAPI api $ angular defAngularOptions +apiJS4 :: Text +apiJS4 = jsForAPI api $ angular defAngularOptions ``` The generated code will be a bit different than previous generators. An extra @@ -379,21 +392,25 @@ nor onError callback functions. ``` javascript + var getPoint = function($http) { return $http( - { url: '/counter' - , method: 'GET' - }); + { url: '/point' + , method: 'GET' + }); } + + var getBooks = function($http, q) { return $http( - { url: '/books' + '?q=' + encodeURIComponent(q), true); + { url: '/books' + '?q=' + encodeURIComponent(q) , method: 'GET' }); } + ``` You can then build your controllers easily @@ -440,8 +457,8 @@ app.service('MyService', function($http) { To do so, you just have to use an alternate generator. ``` haskell -apiJS :: String -apiJS = jsForAPI api $ angularService defAngularOptions +apiJS5 :: Text +apiJS5 = jsForAPI api $ angularService defAngularOptions ``` Again, it is possible to customize some portions with the options. @@ -468,32 +485,34 @@ Servant comes with three name builders included: Keeping the JQuery as an example, let's see the impact: ``` haskell -apiJS :: String -apiJS = jsForAPI api $ jqueryWith defCommonGeneratorOptions { functionNameBuilder= snakeCase } +apiJS6 :: Text +apiJS6 = jsForAPI api $ jqueryWith defCommonGeneratorOptions { functionNameBuilder= snakeCase } ``` -This `String` contains 2 Javascript functions: +This `Text` contains 2 Javascript functions: ``` javascript -var getPoint = function (onSuccess, onError) + +var get_point = function(onSuccess, onError) { $.ajax( { url: '/point' , success: onSuccess , error: onError - , method: 'GET' + , type: 'GET' }); -}; +} -var getBooks = function (q, onSuccess, onError) +var get_books = function(q, onSuccess, onError) { $.ajax( { url: '/books' + '?q=' + encodeURIComponent(q) , success: onSuccess , error: onError - , method: 'GET' + , type: 'GET' }); -}; +} + ``` diff --git a/doc/tutorial/test/JavascriptSpec.hs b/doc/tutorial/test/JavascriptSpec.hs index 2d6007a5..7dfd4cec 100644 --- a/doc/tutorial/test/JavascriptSpec.hs +++ b/doc/tutorial/test/JavascriptSpec.hs @@ -15,7 +15,10 @@ spec = do describe "apiJS" $ do it "is contained verbatim in Javascript.lhs" $ do code <- readFile "Javascript.lhs" - cs apiJS `shouldSatisfy` (`isInfixOf` code) + cs apiJS1 `shouldSatisfy` (`isInfixOf` code) + cs apiJS3 `shouldSatisfy` (`isInfixOf` code) + cs apiJS4 `shouldSatisfy` (`isInfixOf` code) + cs apiJS6 `shouldSatisfy` (`isInfixOf` code) describe "writeJSFiles" $ do it "[not a test] write apiJS to static/api.js" $ do @@ -24,7 +27,7 @@ spec = do describe "app" $ with (return app) $ do context "/api.js" $ do it "delivers apiJS" $ do - get "/api.js" `shouldRespondWith` (fromString (cs apiJS)) + get "/api.js" `shouldRespondWith` (fromString (cs apiJS1)) context "/" $ do it "delivers something" $ do diff --git a/servant-js/src/Servant/JS.hs b/servant-js/src/Servant/JS.hs index 498d45da..d11c6eb0 100644 --- a/servant-js/src/Servant/JS.hs +++ b/servant-js/src/Servant/JS.hs @@ -112,6 +112,7 @@ module Servant.JS , javascript , NoTypes , GenerateList(..) + , FunctionName(..) ) where import Prelude hiding (writeFile) From b22e4e72f968bcbad8dff9cee1b58cbc4275e884 Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Mon, 15 Aug 2016 15:39:07 -0300 Subject: [PATCH 23/43] Review fixes --- doc/tutorial/Javascript.lhs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/tutorial/Javascript.lhs b/doc/tutorial/Javascript.lhs index 4bc1466e..2d9ab6bd 100644 --- a/doc/tutorial/Javascript.lhs +++ b/doc/tutorial/Javascript.lhs @@ -224,17 +224,17 @@ available options: data CommonGeneratorOptions = CommonGeneratorOptions { -- | function generating function names - functionNameBuilder :: FunctionName -> String + functionNameBuilder :: FunctionName -> Text -- | name used when a user want to send the request body (to let you redefine it) - , requestBody :: String + , requestBody :: Text -- | name of the callback parameter when the request was successful - , successCallback :: String + , successCallback :: Text -- | name of the callback parameter when the request reported an error - , errorCallback :: String + , errorCallback :: Text -- | namespace on which we define the js function (empty mean local var) - , moduleName :: String + , moduleName :: Text -- | a prefix that should be prepended to the URL in the generated JS - , urlPrefix :: String + , urlPrefix :: Text } ``` @@ -363,9 +363,9 @@ data AxiosOptions = AxiosOptions -- should be made using credentials withCredentials :: !Bool -- | the name of the cookie to use as a value for xsrf token - , xsrfCookieName :: !(Maybe String) + , xsrfCookieName :: !(Maybe Text) -- | the name of the header to use as a value for xsrf token - , xsrfHeaderName :: !(Maybe String) + , xsrfHeaderName :: !(Maybe Text) } ``` @@ -466,11 +466,11 @@ Again, it is possible to customize some portions with the options. ``` haskell data AngularOptions = AngularOptions { -- | When generating code with wrapInService, name of the service to generate, default is 'app' - serviceName :: String + serviceName :: Text , -- | beginning of the service definition - prologue :: String -> String -> String + prologue :: Text -> Text -> Text , -- | end of the service definition - epilogue :: String + epilogue :: Text } ``` From 23493d982d0ec591f115bc4a0414fbc0dd7b61e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Wed, 17 Aug 2016 15:51:10 +0200 Subject: [PATCH 24/43] add Raw to ComprehensiveAPI (and add ComprehensiveAPIWithoutRaw) --- servant-js/test/Servant/JSSpec.hs | 2 +- .../Servant/API/Internal/Test/ComprehensiveAPI.hs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/servant-js/test/Servant/JSSpec.hs b/servant-js/test/Servant/JSSpec.hs index 5a53b00a..7cf56be0 100644 --- a/servant-js/test/Servant/JSSpec.hs +++ b/servant-js/test/Servant/JSSpec.hs @@ -33,7 +33,7 @@ import Servant.JSSpec.CustomHeaders -- * comprehensive api -- This declaration simply checks that all instances are in place. -_ = jsForAPI comprehensiveAPI vanillaJS :: Text +_ = jsForAPI comprehensiveAPIWithoutRaw vanillaJS :: Text -- * specs diff --git a/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs b/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs index f568e56f..e7c15633 100644 --- a/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs +++ b/servant/src/Servant/API/Internal/Test/ComprehensiveAPI.hs @@ -13,6 +13,13 @@ import Servant.API type GET = Get '[JSON] NoContent type ComprehensiveAPI = + ComprehensiveAPIWithoutRaw :<|> + Raw + +comprehensiveAPI :: Proxy ComprehensiveAPI +comprehensiveAPI = Proxy + +type ComprehensiveAPIWithoutRaw = GET :<|> Get '[JSON] Int :<|> Capture "foo" Int :> GET :<|> @@ -22,7 +29,6 @@ type ComprehensiveAPI = QueryParam "foo" Int :> GET :<|> QueryParams "foo" Int :> GET :<|> QueryFlag "foo" :> GET :<|> --- Raw :<|> RemoteHost :> GET :<|> ReqBody '[JSON] Int :> GET :<|> Get '[JSON] (Headers '[Header "foo" Int] NoContent) :<|> @@ -33,5 +39,5 @@ type ComprehensiveAPI = WithNamedContext "foo" '[] GET :<|> CaptureAll "foo" Int :> GET -comprehensiveAPI :: Proxy ComprehensiveAPI -comprehensiveAPI = Proxy +comprehensiveAPIWithoutRaw :: Proxy ComprehensiveAPIWithoutRaw +comprehensiveAPIWithoutRaw = Proxy From a1b23018f97add4ccc56fbb50906f8fe0aa89ab5 Mon Sep 17 00:00:00 2001 From: Alex Mason Date: Fri, 2 Sep 2016 19:46:49 +1000 Subject: [PATCH 25/43] Replace use of ToByteString with HttpApiData for GetHeaders, fixes servant/#581 * Version bump because this changes the API for GetHeaders --- servant-mock/test/Servant/MockSpec.hs | 9 +++++---- servant/CHANGELOG.md | 1 + servant/servant.cabal | 2 +- servant/src/Servant/API/ResponseHeaders.hs | 15 ++++++++------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/servant-mock/test/Servant/MockSpec.hs b/servant-mock/test/Servant/MockSpec.hs index 7d7b32ac..dfbcc0b9 100644 --- a/servant-mock/test/Servant/MockSpec.hs +++ b/servant-mock/test/Servant/MockSpec.hs @@ -8,9 +8,7 @@ module Servant.MockSpec where import Data.Aeson as Aeson -import Data.ByteString.Conversion.To import Data.Proxy -import Data.String import GHC.Generics import Network.Wai import Servant.API @@ -40,8 +38,11 @@ data TestHeader | ArbitraryHeader deriving (Show) -instance ToByteString TestHeader where - builder = fromString . show +instance ToHttpApiData TestHeader where + toHeader = toHeader . show + toUrlPiece _ = error "ToHttpApiData.toUrlPiece not implemented for TestHeader" + toQueryParam _ = error "ToHttpApiData.toQueryParam not implemented for TestHeader" + instance Arbitrary TestHeader where arbitrary = return ArbitraryHeader diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index f35679d1..bff2ed3e 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -2,6 +2,7 @@ next ---- * Add `CaptureAll` combinator. Captures all of the remaining segments in a URL. +* replace use of `ToByteString` with `HttpApiData` for `GetHeaders` 0.8 --- diff --git a/servant/servant.cabal b/servant/servant.cabal index 3c89171f..0f308995 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -1,5 +1,5 @@ name: servant -version: 0.8 +version: 0.8.1 synopsis: A family of combinators for defining webservices APIs description: A family of combinators for defining webservices APIs and serving them diff --git a/servant/src/Servant/API/ResponseHeaders.hs b/servant/src/Servant/API/ResponseHeaders.hs index cdb7341e..d70f78b5 100644 --- a/servant/src/Servant/API/ResponseHeaders.hs +++ b/servant/src/Servant/API/ResponseHeaders.hs @@ -31,8 +31,9 @@ module Servant.API.ResponseHeaders ) where import Data.ByteString.Char8 as BS (pack, unlines, init) -import Data.ByteString.Conversion (ToByteString, toByteString', +import Data.ByteString.Conversion (--ToByteString, toByteString', FromByteString, fromByteString) +import Web.HttpApiData (ToHttpApiData,toHeader) import qualified Data.CaseInsensitive as CI import Data.Proxy import GHC.TypeLits (KnownSymbol, symbolVal) @@ -88,18 +89,18 @@ class GetHeaders ls where instance OVERLAPPING_ GetHeaders (HList '[]) where getHeaders _ = [] -instance OVERLAPPABLE_ ( KnownSymbol h, ToByteString x, GetHeaders (HList xs) ) +instance OVERLAPPABLE_ ( KnownSymbol h, ToHttpApiData x, GetHeaders (HList xs) ) => GetHeaders (HList (Header h x ': xs)) where getHeaders hdrs = case hdrs of - Header val `HCons` rest -> (headerName , toByteString' val):getHeaders rest - UndecodableHeader h `HCons` rest -> (headerName, h) : getHeaders rest + Header val `HCons` rest -> (headerName , toHeader val):getHeaders rest + UndecodableHeader h `HCons` rest -> (headerName, h) :getHeaders rest MissingHeader `HCons` rest -> getHeaders rest where headerName = CI.mk . pack $ symbolVal (Proxy :: Proxy h) instance OVERLAPPING_ GetHeaders (Headers '[] a) where getHeaders _ = [] -instance OVERLAPPABLE_ ( KnownSymbol h, GetHeaders (HList rest), ToByteString v ) +instance OVERLAPPABLE_ ( KnownSymbol h, GetHeaders (HList rest), ToHttpApiData v ) => GetHeaders (Headers (Header h v ': rest) a) where getHeaders hs = getHeaders $ getHeadersHList hs @@ -111,11 +112,11 @@ class AddHeader h v orig new addHeader :: v -> orig -> new -- ^ N.B.: The same header can't be added multiple times -instance OVERLAPPING_ ( KnownSymbol h, ToByteString v ) +instance OVERLAPPING_ ( KnownSymbol h, ToHttpApiData v ) => AddHeader h v (Headers (fst ': rest) a) (Headers (Header h v ': fst ': rest) a) where addHeader a (Headers resp heads) = Headers resp (HCons (Header a) heads) -instance OVERLAPPABLE_ ( KnownSymbol h, ToByteString v +instance OVERLAPPABLE_ ( KnownSymbol h, ToHttpApiData v , new ~ (Headers '[Header h v] a) ) => AddHeader h v a new where addHeader a resp = Headers resp (HCons (Header a) HNil) From 85a6d9714cd9d8cf9cc1dab08b46f1add25727d5 Mon Sep 17 00:00:00 2001 From: Christine Koppelt Date: Fri, 2 Sep 2016 12:43:22 +0200 Subject: [PATCH 26/43] add 'stack setup' command --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4eb4705..58aaca53 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,9 +8,10 @@ repository. You can use `cabal`: ./scripts/test-all.sh # Run all the tests ``` -`stack`: +Or `stack`: ```shell +stack setup # Downloads and installs a proper GHC version if necessary stack build # Install and build packages stack test # Run all the tests ``` From 9b5a564f3c151c699a6ee16c7e0dfc9564fcc1f8 Mon Sep 17 00:00:00 2001 From: Alex Mason Date: Fri, 2 Sep 2016 23:14:45 +1000 Subject: [PATCH 27/43] Remove dependency on bytestring-conversion --- servant/servant.cabal | 1 - servant/src/Servant/API/ResponseHeaders.hs | 13 ++++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/servant/servant.cabal b/servant/servant.cabal index 0f308995..56a2cc6e 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -54,7 +54,6 @@ library , aeson >= 0.7 && < 1.1 , attoparsec >= 0.12 && < 0.14 , bytestring >= 0.10 && < 0.11 - , bytestring-conversion >= 0.3 && < 0.4 , case-insensitive >= 1.2 && < 1.3 , http-api-data >= 0.1 && < 0.3 , http-media >= 0.4 && < 0.7 diff --git a/servant/src/Servant/API/ResponseHeaders.hs b/servant/src/Servant/API/ResponseHeaders.hs index d70f78b5..1ddba9d0 100644 --- a/servant/src/Servant/API/ResponseHeaders.hs +++ b/servant/src/Servant/API/ResponseHeaders.hs @@ -31,9 +31,8 @@ module Servant.API.ResponseHeaders ) where import Data.ByteString.Char8 as BS (pack, unlines, init) -import Data.ByteString.Conversion (--ToByteString, toByteString', - FromByteString, fromByteString) -import Web.HttpApiData (ToHttpApiData,toHeader) +import Web.HttpApiData (ToHttpApiData,toHeader + ,FromHttpApiData,parseHeader) import qualified Data.CaseInsensitive as CI import Data.Proxy import GHC.TypeLits (KnownSymbol, symbolVal) @@ -69,17 +68,17 @@ class BuildHeadersTo hs where instance OVERLAPPING_ BuildHeadersTo '[] where buildHeadersTo _ = HNil -instance OVERLAPPABLE_ ( FromByteString v, BuildHeadersTo xs, KnownSymbol h ) +instance OVERLAPPABLE_ ( FromHttpApiData v, BuildHeadersTo xs, KnownSymbol h ) => BuildHeadersTo ((Header h v) ': xs) where buildHeadersTo headers = let wantedHeader = CI.mk . pack $ symbolVal (Proxy :: Proxy h) matching = snd <$> filter (\(h, _) -> h == wantedHeader) headers in case matching of [] -> MissingHeader `HCons` buildHeadersTo headers - xs -> case fromByteString (BS.init $ BS.unlines xs) of - Nothing -> UndecodableHeader (BS.init $ BS.unlines xs) + xs -> case parseHeader (BS.init $ BS.unlines xs) of + Left _err -> UndecodableHeader (BS.init $ BS.unlines xs) `HCons` buildHeadersTo headers - Just h -> Header h `HCons` buildHeadersTo headers + Right h -> Header h `HCons` buildHeadersTo headers -- * Getting From 5bef76ed03772d271be55801f6bc480cbe8f7baf Mon Sep 17 00:00:00 2001 From: Alex Mason Date: Fri, 2 Sep 2016 23:27:37 +1000 Subject: [PATCH 28/43] Remove all use of bytestring-conversion --- servant-docs/servant-docs.cabal | 2 -- servant-docs/src/Servant/Docs/Internal.hs | 5 ++--- servant-mock/servant-mock.cabal | 1 - servant-server/servant-server.cabal | 1 - servant-server/test/Servant/ServerSpec.hs | 1 - 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/servant-docs/servant-docs.cabal b/servant-docs/servant-docs.cabal index d325e3d9..d34ac9b4 100644 --- a/servant-docs/servant-docs.cabal +++ b/servant-docs/servant-docs.cabal @@ -36,7 +36,6 @@ library , aeson , aeson-pretty , bytestring - , bytestring-conversion , case-insensitive , hashable , http-media >= 0.6 @@ -61,7 +60,6 @@ executable greet-docs build-depends: base , aeson - , bytestring-conversion , lens , servant , servant-docs diff --git a/servant-docs/src/Servant/Docs/Internal.hs b/servant-docs/src/Servant/Docs/Internal.hs index 7b181822..70530455 100644 --- a/servant-docs/src/Servant/Docs/Internal.hs +++ b/servant-docs/src/Servant/Docs/Internal.hs @@ -25,7 +25,6 @@ import Control.Arrow (second) import Control.Lens (makeLenses, mapped, over, traversed, view, (%~), (&), (.~), (<>~), (^.), (|>)) import qualified Control.Monad.Omega as Omega -import Data.ByteString.Conversion (ToByteString, toByteString) import Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.ByteString.Char8 as BSC import qualified Data.CaseInsensitive as CI @@ -461,12 +460,12 @@ class AllHeaderSamples ls where instance AllHeaderSamples '[] where allHeaderToSample _ = [] -instance (ToByteString l, AllHeaderSamples ls, ToSample l, KnownSymbol h) +instance (ToHttpApiData l, AllHeaderSamples ls, ToSample l, KnownSymbol h) => AllHeaderSamples (Header h l ': ls) where allHeaderToSample _ = mkHeader (toSample (Proxy :: Proxy l)) : allHeaderToSample (Proxy :: Proxy ls) where headerName = CI.mk . cs $ symbolVal (Proxy :: Proxy h) - mkHeader (Just x) = (headerName, cs $ toByteString x) + mkHeader (Just x) = (headerName, cs $ toHeader x) mkHeader Nothing = (headerName, "") -- | Synthesise a sample value of a type, encoded in the specified media types. diff --git a/servant-mock/servant-mock.cabal b/servant-mock/servant-mock.cabal index 89ff1f6f..64e012b0 100644 --- a/servant-mock/servant-mock.cabal +++ b/servant-mock/servant-mock.cabal @@ -69,5 +69,4 @@ test-suite spec servant-server, servant-mock, aeson, - bytestring-conversion, wai diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index d055df2f..44a787fa 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -114,7 +114,6 @@ test-suite spec , aeson , base64-bytestring , bytestring - , bytestring-conversion , directory , exceptions , hspec == 2.* diff --git a/servant-server/test/Servant/ServerSpec.hs b/servant-server/test/Servant/ServerSpec.hs index 2337c258..ade5a7b3 100644 --- a/servant-server/test/Servant/ServerSpec.hs +++ b/servant-server/test/Servant/ServerSpec.hs @@ -17,7 +17,6 @@ import Control.Monad (forM_, when, unless) import Control.Monad.Trans.Except (throwE) import Data.Aeson (FromJSON, ToJSON, decode', encode) import qualified Data.ByteString.Base64 as Base64 -import Data.ByteString.Conversion () import Data.Char (toUpper) import Data.Monoid import Data.Proxy (Proxy (Proxy)) From a991cbe77c0a951a39b942fa684b9e9898ac13c2 Mon Sep 17 00:00:00 2001 From: Alex Mason Date: Fri, 2 Sep 2016 23:53:18 +1000 Subject: [PATCH 29/43] Revert version bump, update changelog --- servant/CHANGELOG.md | 2 +- servant/servant.cabal | 2 +- servant/src/Servant/API/ResponseHeaders.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index bff2ed3e..f5973555 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -2,7 +2,7 @@ next ---- * Add `CaptureAll` combinator. Captures all of the remaining segments in a URL. -* replace use of `ToByteString` with `HttpApiData` for `GetHeaders` +* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo` 0.8 --- diff --git a/servant/servant.cabal b/servant/servant.cabal index 56a2cc6e..d3607935 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -1,5 +1,5 @@ name: servant -version: 0.8.1 +version: 0.8 synopsis: A family of combinators for defining webservices APIs description: A family of combinators for defining webservices APIs and serving them diff --git a/servant/src/Servant/API/ResponseHeaders.hs b/servant/src/Servant/API/ResponseHeaders.hs index 1ddba9d0..99bd72b6 100644 --- a/servant/src/Servant/API/ResponseHeaders.hs +++ b/servant/src/Servant/API/ResponseHeaders.hs @@ -31,8 +31,8 @@ module Servant.API.ResponseHeaders ) where import Data.ByteString.Char8 as BS (pack, unlines, init) -import Web.HttpApiData (ToHttpApiData,toHeader - ,FromHttpApiData,parseHeader) +import Web.HttpApiData (ToHttpApiData, toHeader + ,FromHttpApiData, parseHeader) import qualified Data.CaseInsensitive as CI import Data.Proxy import GHC.TypeLits (KnownSymbol, symbolVal) From dbab31508ee21f1ece729a1560d417a726e9c287 Mon Sep 17 00:00:00 2001 From: Alex Mason Date: Fri, 2 Sep 2016 23:59:36 +1000 Subject: [PATCH 30/43] Comma style is hard --- servant/src/Servant/API/ResponseHeaders.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servant/src/Servant/API/ResponseHeaders.hs b/servant/src/Servant/API/ResponseHeaders.hs index 99bd72b6..deacb90b 100644 --- a/servant/src/Servant/API/ResponseHeaders.hs +++ b/servant/src/Servant/API/ResponseHeaders.hs @@ -31,8 +31,8 @@ module Servant.API.ResponseHeaders ) where import Data.ByteString.Char8 as BS (pack, unlines, init) -import Web.HttpApiData (ToHttpApiData, toHeader - ,FromHttpApiData, parseHeader) +import Web.HttpApiData (ToHttpApiData, toHeader, + FromHttpApiData, parseHeader) import qualified Data.CaseInsensitive as CI import Data.Proxy import GHC.TypeLits (KnownSymbol, symbolVal) From 3e704362c34a730aee05f187bbdcf87ffd658214 Mon Sep 17 00:00:00 2001 From: Alex Mason Date: Sat, 3 Sep 2016 00:23:21 +1000 Subject: [PATCH 31/43] Remove error calls and replace with working implementations of ToHttpApiData --- servant-mock/test/Servant/MockSpec.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servant-mock/test/Servant/MockSpec.hs b/servant-mock/test/Servant/MockSpec.hs index dfbcc0b9..83401c73 100644 --- a/servant-mock/test/Servant/MockSpec.hs +++ b/servant-mock/test/Servant/MockSpec.hs @@ -40,8 +40,8 @@ data TestHeader instance ToHttpApiData TestHeader where toHeader = toHeader . show - toUrlPiece _ = error "ToHttpApiData.toUrlPiece not implemented for TestHeader" - toQueryParam _ = error "ToHttpApiData.toQueryParam not implemented for TestHeader" + toUrlPiece = toUrlPiece . show + toQueryParam = toQueryParam . show instance Arbitrary TestHeader where From 47eed38ee6a595ac24fd7b550c5efe9043890af5 Mon Sep 17 00:00:00 2001 From: Cornelius Diekmann Date: Fri, 2 Sep 2016 16:38:03 +0200 Subject: [PATCH 32/43] Fixed typo: s/real/realm/ Actually, I'm just trying to learn servant and I'm reading the doc for the first time. But this just looks like a typo to me. --- doc/tutorial/ApiType.lhs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorial/ApiType.lhs b/doc/tutorial/ApiType.lhs index 096903e8..3aa4c844 100644 --- a/doc/tutorial/ApiType.lhs +++ b/doc/tutorial/ApiType.lhs @@ -319,8 +319,8 @@ Which is used like so: ``` haskell type ProtectedAPI12 - = UserAPI -- this is public - :<|> BasicAuth "my-real" User :> UserAPI2 -- this is protected by auth + = UserAPI -- this is public + :<|> BasicAuth "my-realm" User :> UserAPI2 -- this is protected by auth ``` ### Interoperability with `wai`: `Raw` From bf824a3889f4c1483ffc6ad816f9f6ae87ddbbcc Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Fri, 2 Sep 2016 13:09:47 -0300 Subject: [PATCH 33/43] Bump version to 0.8.1 --- doc/tutorial/tutorial.cabal | 2 +- servant-client/servant-client.cabal | 2 +- servant-docs/servant-docs.cabal | 2 +- servant-foreign/servant-foreign.cabal | 2 +- servant-js/servant-js.cabal | 2 +- servant-mock/servant-mock.cabal | 2 +- servant-server/servant-server.cabal | 2 +- servant/CHANGELOG.md | 2 +- servant/servant.cabal | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/tutorial/tutorial.cabal b/doc/tutorial/tutorial.cabal index 3b90c0dc..797ff5e2 100644 --- a/doc/tutorial/tutorial.cabal +++ b/doc/tutorial/tutorial.cabal @@ -1,5 +1,5 @@ name: tutorial -version: 0.8 +version: 0.8.1 synopsis: The servant tutorial homepage: http://haskell-servant.readthedocs.org/ license: BSD3 diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index 089e1209..edbca092 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -1,5 +1,5 @@ name: servant-client -version: 0.8 +version: 0.8.1 synopsis: automatical derivation of querying functions for servant webservices description: This library lets you derive automatically Haskell functions that diff --git a/servant-docs/servant-docs.cabal b/servant-docs/servant-docs.cabal index d325e3d9..a194b763 100644 --- a/servant-docs/servant-docs.cabal +++ b/servant-docs/servant-docs.cabal @@ -1,5 +1,5 @@ name: servant-docs -version: 0.8 +version: 0.8.1 synopsis: generate API docs for your servant webservice description: Library for generating API docs from a servant API definition. diff --git a/servant-foreign/servant-foreign.cabal b/servant-foreign/servant-foreign.cabal index ea5b599a..26be4dbd 100644 --- a/servant-foreign/servant-foreign.cabal +++ b/servant-foreign/servant-foreign.cabal @@ -1,5 +1,5 @@ name: servant-foreign -version: 0.8 +version: 0.8.1 synopsis: Helpers for generating clients for servant APIs in any programming language description: Helper types and functions for generating client functions for servant APIs in any programming language diff --git a/servant-js/servant-js.cabal b/servant-js/servant-js.cabal index 726e0b4e..04ea1ff9 100644 --- a/servant-js/servant-js.cabal +++ b/servant-js/servant-js.cabal @@ -1,5 +1,5 @@ name: servant-js -version: 0.8 +version: 0.8.1 synopsis: Automatically derive javascript functions to query servant webservices. description: Automatically derive javascript functions to query servant webservices. diff --git a/servant-mock/servant-mock.cabal b/servant-mock/servant-mock.cabal index 89ff1f6f..e4610e96 100644 --- a/servant-mock/servant-mock.cabal +++ b/servant-mock/servant-mock.cabal @@ -1,5 +1,5 @@ name: servant-mock -version: 0.8 +version: 0.8.1 synopsis: Derive a mock server for free from your servant API types description: Derive a mock server for free from your servant API types diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index d055df2f..04ad5fa3 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -1,5 +1,5 @@ name: servant-server -version: 0.8 +version: 0.8.1 synopsis: A family of combinators for defining webservices APIs and serving them description: A family of combinators for defining webservices APIs and serving them diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index f35679d1..c409c960 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -1,4 +1,4 @@ -next +0.8.1 ---- * Add `CaptureAll` combinator. Captures all of the remaining segments in a URL. diff --git a/servant/servant.cabal b/servant/servant.cabal index 3c89171f..0f308995 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -1,5 +1,5 @@ name: servant -version: 0.8 +version: 0.8.1 synopsis: A family of combinators for defining webservices APIs description: A family of combinators for defining webservices APIs and serving them From 76e74f9d5a91364df6d3b191d6d1a42810ff2c9f Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Wed, 7 Sep 2016 12:31:54 -0300 Subject: [PATCH 34/43] Add instances for IsSecure --- servant/CHANGELOG.md | 4 ++++ servant/src/Servant/API/IsSecure.hs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index f5786026..41e73639 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -1,3 +1,7 @@ +next +---- +* Added Eq, Show, Read, Generic and Ord instances to IsSecure + 0.8.1 ---- diff --git a/servant/src/Servant/API/IsSecure.hs b/servant/src/Servant/API/IsSecure.hs index 031f94a7..cbf1ab79 100644 --- a/servant/src/Servant/API/IsSecure.hs +++ b/servant/src/Servant/API/IsSecure.hs @@ -1,10 +1,12 @@ {-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} module Servant.API.IsSecure ( -- $issecure IsSecure(..) ) where import Data.Typeable +import GHC.Generics (Generic) -- | Was this request made over an SSL connection? -- @@ -19,7 +21,7 @@ data IsSecure = Secure -- ^ the connection to the server -- is secure (HTTPS) | NotSecure -- ^ the connection to the server -- is not secure (HTTP) - deriving Typeable + deriving (Eq, Show, Read, Generic, Ord, Typeable) -- $issecure -- From 7a7f7d7cf5d8d04e378ee5847362ce1ebd6e88f7 Mon Sep 17 00:00:00 2001 From: Christian Klinger Date: Thu, 8 Sep 2016 00:03:44 +0200 Subject: [PATCH 35/43] changed ClientM to be a Reader of ClientEnv --- doc/tutorial/Client.lhs | 27 +++----- servant-client/CHANGELOG.md | 8 +++ servant-client/servant-client.cabal | 1 + servant-client/src/Servant/Client.hs | 30 +++++---- servant-client/src/Servant/Common/Req.hs | 69 +++++++++++++------ servant-client/test/Servant/ClientSpec.hs | 82 +++++++++++------------ 6 files changed, 126 insertions(+), 91 deletions(-) diff --git a/doc/tutorial/Client.lhs b/doc/tutorial/Client.lhs index a40ca7c6..fa825e2c 100644 --- a/doc/tutorial/Client.lhs +++ b/doc/tutorial/Client.lhs @@ -15,11 +15,10 @@ need to have some language extensions and imports: module Client where -import Control.Monad.Trans.Except (ExceptT, runExceptT) import Data.Aeson import Data.Proxy import GHC.Generics -import Network.HTTP.Client (Manager, newManager, defaultManagerSettings) +import Network.HTTP.Client (newManager, defaultManagerSettings) import Servant.API import Servant.Client ``` @@ -71,19 +70,13 @@ What we are going to get with **servant-client** here is 3 functions, one to que ``` haskell position :: Int -- ^ value for "x" -> Int -- ^ value for "y" - -> Manager -- ^ the HTTP client to use - -> BaseUrl -- ^ the URL at which the API can be found - -> ExceptT ServantError IO Position + -> ClientM Position hello :: Maybe String -- ^ an optional value for "name" - -> Manager -- ^ the HTTP client to use - -> BaseUrl -- ^ the URL at which the API can be found - -> ExceptT ServantError IO HelloMessage + -> ClientM HelloMessage marketing :: ClientInfo -- ^ value for the request body - -> Manager -- ^ the HTTP client to use - -> BaseUrl -- ^ the URL at which the API can be found - -> ExceptT ServantError IO Email + -> ClientM Email ``` Each function makes available as an argument any value that the response may @@ -120,17 +113,17 @@ data BaseUrl = BaseUrl That's it. Let's now write some code that uses our client functions. ``` haskell -queries :: Manager -> BaseUrl -> ExceptT ServantError IO (Position, HelloMessage, Email) -queries manager baseurl = do - pos <- position 10 10 manager baseurl - message <- hello (Just "servant") manager baseurl - em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]) manager baseurl +queries :: ClientM (Position, HelloMessage, Email) +queries = do + pos <- position 10 10 + message <- hello (Just "servant") + em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]) return (pos, message, em) run :: IO () run = do manager <- newManager defaultManagerSettings - res <- runExceptT (queries manager (BaseUrl Http "localhost" 8081 "")) + res <- runClientM queries (ClientEnv manager (BaseUrl Http "localhost" 8081 "")) case res of Left err -> putStrLn $ "Error: " ++ show err Right (pos, message, em) -> do diff --git a/servant-client/CHANGELOG.md b/servant-client/CHANGELOG.md index 3627608d..df037a86 100644 --- a/servant-client/CHANGELOG.md +++ b/servant-client/CHANGELOG.md @@ -1,3 +1,11 @@ +0.8.1 +----- + +* BACKWARDS INCOMPATIBLE: `client` now returns a ClientM which is a Reader for + BasicEnv. BasicEnv comprises the HttpManager and BaseUrl that have had to be + passed to each method returned by `client`. + + 0.7.1 ----- diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index edbca092..c49cfe0e 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -53,6 +53,7 @@ library , text >= 1.2 && < 1.3 , transformers >= 0.3 && < 0.6 , transformers-compat >= 0.4 && < 0.6 + , mtl hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall diff --git a/servant-client/src/Servant/Client.hs b/servant-client/src/Servant/Client.hs index 18581075..0b7bdeac 100644 --- a/servant-client/src/Servant/Client.hs +++ b/servant-client/src/Servant/Client.hs @@ -20,6 +20,8 @@ module Servant.Client , client , HasClient(..) , ClientM + , runClientM + , ClientEnv (ClientEnv) , mkAuthenticateReq , ServantError(..) , module Servant.Common.BaseUrl @@ -34,7 +36,7 @@ import Data.Proxy import Data.String.Conversions import Data.Text (unpack) import GHC.TypeLits -import Network.HTTP.Client (Manager, Response) +import Network.HTTP.Client (Response) import Network.HTTP.Media import qualified Network.HTTP.Types as H import qualified Network.HTTP.Types.Header as HTTP @@ -154,17 +156,17 @@ instance OVERLAPPABLE_ -- Note [Non-Empty Content Types] (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts) ) => HasClient (Verb method status cts' a) where - type Client (Verb method status cts' a) = Manager -> BaseUrl -> ClientM a - clientWithRoute Proxy req manager baseurl = - snd <$> performRequestCT (Proxy :: Proxy ct) method req manager baseurl + type Client (Verb method status cts' a) = ClientM a + clientWithRoute Proxy req = do + snd <$> performRequestCT (Proxy :: Proxy ct) method req where method = reflectMethod (Proxy :: Proxy method) instance OVERLAPPING_ (ReflectMethod method) => HasClient (Verb method status cts NoContent) where type Client (Verb method status cts NoContent) - = Manager -> BaseUrl -> ClientM NoContent - clientWithRoute Proxy req manager baseurl = - performRequestNoBody method req manager baseurl >> return NoContent + = ClientM NoContent + clientWithRoute Proxy req = do + performRequestNoBody method req >> return NoContent where method = reflectMethod (Proxy :: Proxy method) instance OVERLAPPING_ @@ -172,10 +174,10 @@ instance OVERLAPPING_ ( MimeUnrender ct a, BuildHeadersTo ls, ReflectMethod method, cts' ~ (ct ': cts) ) => HasClient (Verb method status cts' (Headers ls a)) where type Client (Verb method status cts' (Headers ls a)) - = Manager -> BaseUrl -> ClientM (Headers ls a) - clientWithRoute Proxy req manager baseurl = do + = ClientM (Headers ls a) + clientWithRoute Proxy req = do let method = reflectMethod (Proxy :: Proxy method) - (hdrs, resp) <- performRequestCT (Proxy :: Proxy ct) method req manager baseurl + (hdrs, resp) <- performRequestCT (Proxy :: Proxy ct) method req return $ Headers { getResponse = resp , getHeadersHList = buildHeadersTo hdrs } @@ -184,10 +186,10 @@ instance OVERLAPPING_ ( BuildHeadersTo ls, ReflectMethod method ) => HasClient (Verb method status cts (Headers ls NoContent)) where type Client (Verb method status cts (Headers ls NoContent)) - = Manager -> BaseUrl -> ClientM (Headers ls NoContent) - clientWithRoute Proxy req manager baseurl = do + = ClientM (Headers ls NoContent) + clientWithRoute Proxy req = do let method = reflectMethod (Proxy :: Proxy method) - hdrs <- performRequestNoBody method req manager baseurl + hdrs <- performRequestNoBody method req return $ Headers { getResponse = NoContent , getHeadersHList = buildHeadersTo hdrs } @@ -372,7 +374,7 @@ instance (KnownSymbol sym, HasClient api) -- back the full `Response`. instance HasClient Raw where type Client Raw - = H.Method -> Manager -> BaseUrl -> ClientM (Int, ByteString, MediaType, [HTTP.Header], Response ByteString) + = H.Method -> ClientM (Int, ByteString, MediaType, [HTTP.Header], Response ByteString) clientWithRoute :: Proxy Raw -> Req -> Client Raw clientWithRoute Proxy req httpMethod = do diff --git a/servant-client/src/Servant/Common/Req.hs b/servant-client/src/Servant/Common/Req.hs index ea610cce..7a8d7a1e 100644 --- a/servant-client/src/Servant/Common/Req.hs +++ b/servant-client/src/Servant/Common/Req.hs @@ -1,7 +1,10 @@ {-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + module Servant.Common.Req where #if !MIN_VERSION_base(4,8,0) @@ -10,8 +13,18 @@ import Control.Applicative import Control.Exception import Control.Monad import Control.Monad.Catch (MonadThrow) -import Control.Monad.IO.Class + +#if MIN_VERSION_mtl(2,2,0) +import Control.Monad.Except (MonadError(..)) +#else +import Control.Monad.Error.Class (MonadError(..)) +#endif import Control.Monad.Trans.Except + + +import GHC.Generics +import Control.Monad.IO.Class () +import Control.Monad.Reader import Data.ByteString.Lazy hiding (pack, filter, map, null, elem) import Data.String import Data.String.Conversions @@ -19,9 +32,9 @@ import Data.Proxy import Data.Text (Text) import Data.Text.Encoding import Data.Typeable -import Network.HTTP.Client hiding (Proxy, path) import Network.HTTP.Media import Network.HTTP.Types +import Network.HTTP.Client hiding (Proxy, path) import qualified Network.HTTP.Types.Header as HTTP import Network.URI hiding (path) import Servant.API.ContentTypes @@ -149,20 +162,40 @@ parseRequest url = liftM disableStatusCheck (parseUrl url) displayHttpRequest :: Method -> String displayHttpRequest httpmethod = "HTTP " ++ cs httpmethod ++ " request" -type ClientM = ExceptT ServantError IO +data ClientEnv + = ClientEnv + { manager :: Manager + , baseUrl :: BaseUrl + } -performRequest :: Method -> Req -> Manager -> BaseUrl + +-- | @ClientM@ is the monad in which client functions run. Contains the +-- 'Manager' and 'BaseUrl' used for requests in the reader environment. + +newtype ClientM a = ClientM { runClientM' :: ReaderT ClientEnv (ExceptT ServantError IO) a } + deriving ( Functor, Applicative, Monad, MonadIO, Generic + , MonadReader ClientEnv + , MonadError ServantError + ) + +runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a) +runClientM cm env = runExceptT $ (flip runReaderT env) $ runClientM' cm + + +performRequest :: Method -> Req -> ClientM ( Int, ByteString, MediaType , [HTTP.Header], Response ByteString) -performRequest reqMethod req manager reqHost = do +performRequest reqMethod req = do + m <- asks manager + reqHost <- asks baseUrl partialRequest <- liftIO $ reqToRequest req reqHost let request = partialRequest { Client.method = reqMethod } - eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request manager + eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m case eResponse of Left err -> - throwE . ConnectionError $ SomeException err + throwError . ConnectionError $ SomeException err Right response -> do let status = Client.responseStatus response @@ -172,28 +205,26 @@ performRequest reqMethod req manager reqHost = do ct <- case lookup "Content-Type" $ Client.responseHeaders response of Nothing -> pure $ "application"//"octet-stream" Just t -> case parseAccept t of - Nothing -> throwE $ InvalidContentTypeHeader (cs t) body + Nothing -> throwError $ InvalidContentTypeHeader (cs t) body Just t' -> pure t' unless (status_code >= 200 && status_code < 300) $ - throwE $ FailureResponse status ct body + throwError $ FailureResponse status ct body return (status_code, body, ct, hdrs, response) -performRequestCT :: MimeUnrender ct result => - Proxy ct -> Method -> Req -> Manager -> BaseUrl +performRequestCT :: MimeUnrender ct result => Proxy ct -> Method -> Req -> ClientM ([HTTP.Header], result) -performRequestCT ct reqMethod req manager reqHost = do +performRequestCT ct reqMethod req = do let acceptCT = contentType ct (_status, respBody, respCT, hdrs, _response) <- - performRequest reqMethod (req { reqAccept = [acceptCT] }) manager reqHost - unless (matches respCT (acceptCT)) $ throwE $ UnsupportedContentType respCT respBody + performRequest reqMethod (req { reqAccept = [acceptCT] }) + unless (matches respCT (acceptCT)) $ throwError $ UnsupportedContentType respCT respBody case mimeUnrender ct respBody of - Left err -> throwE $ DecodeFailure err respCT respBody + Left err -> throwError $ DecodeFailure err respCT respBody Right val -> return (hdrs, val) -performRequestNoBody :: Method -> Req -> Manager -> BaseUrl - -> ClientM [HTTP.Header] -performRequestNoBody reqMethod req manager reqHost = do - (_status, _body, _ct, hdrs, _response) <- performRequest reqMethod req manager reqHost +performRequestNoBody :: Method -> Req -> ClientM [HTTP.Header] +performRequestNoBody reqMethod req = do + (_status, _body, _ct, hdrs, _response) <- performRequest reqMethod req return hdrs catchConnectionError :: IO a -> IO (Either ServantError a) diff --git a/servant-client/test/Servant/ClientSpec.hs b/servant-client/test/Servant/ClientSpec.hs index da7c763b..1f0be75b 100644 --- a/servant-client/test/Servant/ClientSpec.hs +++ b/servant-client/test/Servant/ClientSpec.hs @@ -32,7 +32,7 @@ import Control.Applicative ((<$>)) import Control.Arrow (left) import Control.Concurrent (forkIO, killThread, ThreadId) import Control.Exception (bracket) -import Control.Monad.Trans.Except (throwE, runExceptT) +import Control.Monad.Trans.Except (throwE ) import Data.Aeson import qualified Data.ByteString.Lazy as BS import Data.Char (chr, isPrint) @@ -123,22 +123,22 @@ type Api = api :: Proxy Api api = Proxy -getGet :: C.Manager -> BaseUrl -> SCR.ClientM Person -getDeleteEmpty :: C.Manager -> BaseUrl -> SCR.ClientM NoContent -getCapture :: String -> C.Manager -> BaseUrl -> SCR.ClientM Person -getCaptureAll :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM [Person] -getBody :: Person -> C.Manager -> BaseUrl -> SCR.ClientM Person -getQueryParam :: Maybe String -> C.Manager -> BaseUrl -> SCR.ClientM Person -getQueryParams :: [String] -> C.Manager -> BaseUrl -> SCR.ClientM [Person] -getQueryFlag :: Bool -> C.Manager -> BaseUrl -> SCR.ClientM Bool -getRawSuccess :: HTTP.Method -> C.Manager -> BaseUrl +getGet :: SCR.ClientM Person +getDeleteEmpty :: SCR.ClientM NoContent +getCapture :: String -> SCR.ClientM Person +getCaptureAll :: [String] -> SCR.ClientM [Person] +getBody :: Person -> SCR.ClientM Person +getQueryParam :: Maybe String -> SCR.ClientM Person +getQueryParams :: [String] -> SCR.ClientM [Person] +getQueryFlag :: Bool -> SCR.ClientM Bool +getRawSuccess :: HTTP.Method -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString) -getRawFailure :: HTTP.Method -> C.Manager -> BaseUrl +getRawFailure :: HTTP.Method -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString) -getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])] -> C.Manager -> BaseUrl +getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])] -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])]) -getRespHeaders :: C.Manager -> BaseUrl -> SCR.ClientM (Headers TestHeaders Bool) -getDeleteContentType :: C.Manager -> BaseUrl -> SCR.ClientM NoContent +getRespHeaders :: SCR.ClientM (Headers TestHeaders Bool) +getDeleteContentType :: SCR.ClientM NoContent getGet :<|> getDeleteEmpty :<|> getCapture @@ -242,42 +242,42 @@ sucessSpec :: Spec sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do it "Servant.API.Get" $ \(_, baseUrl) -> do - (left show <$> runExceptT (getGet manager baseUrl)) `shouldReturn` Right alice + (left show <$> (runClientM getGet (ClientEnv manager baseUrl))) `shouldReturn` Right alice describe "Servant.API.Delete" $ do it "allows empty content type" $ \(_, baseUrl) -> do - (left show <$> runExceptT (getDeleteEmpty manager baseUrl)) `shouldReturn` Right NoContent + (left show <$> (runClientM getDeleteEmpty (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent it "allows content type" $ \(_, baseUrl) -> do - (left show <$> runExceptT (getDeleteContentType manager baseUrl)) `shouldReturn` Right NoContent + (left show <$> (runClientM getDeleteContentType (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent it "Servant.API.Capture" $ \(_, baseUrl) -> do - (left show <$> runExceptT (getCapture "Paula" manager baseUrl)) `shouldReturn` Right (Person "Paula" 0) + (left show <$> (runClientM (getCapture "Paula") (ClientEnv manager baseUrl))) `shouldReturn` Right (Person "Paula" 0) it "Servant.API.CaptureAll" $ \(_, baseUrl) -> do let expected = [(Person "Paula" 0), (Person "Peta" 1)] - (left show <$> runExceptT (getCaptureAll ["Paula", "Peta"] manager baseUrl)) `shouldReturn` Right expected + (left show <$> (runClientM (getCaptureAll ["Paula", "Peta"]) (ClientEnv manager baseUrl))) `shouldReturn` Right expected it "Servant.API.ReqBody" $ \(_, baseUrl) -> do let p = Person "Clara" 42 - (left show <$> runExceptT (getBody p manager baseUrl)) `shouldReturn` Right p + (left show <$> runClientM (getBody p) (ClientEnv manager baseUrl)) `shouldReturn` Right p it "Servant.API.QueryParam" $ \(_, baseUrl) -> do - left show <$> runExceptT (getQueryParam (Just "alice") manager baseUrl) `shouldReturn` Right alice - Left FailureResponse{..} <- runExceptT (getQueryParam (Just "bob") manager baseUrl) + left show <$> runClientM (getQueryParam (Just "alice")) (ClientEnv manager baseUrl) `shouldReturn` Right alice + Left FailureResponse{..} <- runClientM (getQueryParam (Just "bob")) (ClientEnv manager baseUrl) responseStatus `shouldBe` HTTP.Status 400 "bob not found" it "Servant.API.QueryParam.QueryParams" $ \(_, baseUrl) -> do - (left show <$> runExceptT (getQueryParams [] manager baseUrl)) `shouldReturn` Right [] - (left show <$> runExceptT (getQueryParams ["alice", "bob"] manager baseUrl)) + (left show <$> runClientM (getQueryParams []) (ClientEnv manager baseUrl)) `shouldReturn` Right [] + (left show <$> runClientM (getQueryParams ["alice", "bob"]) (ClientEnv manager baseUrl)) `shouldReturn` Right [Person "alice" 0, Person "bob" 1] context "Servant.API.QueryParam.QueryFlag" $ forM_ [False, True] $ \ flag -> it (show flag) $ \(_, baseUrl) -> do - (left show <$> runExceptT (getQueryFlag flag manager baseUrl)) `shouldReturn` Right flag + (left show <$> runClientM (getQueryFlag flag) (ClientEnv manager baseUrl)) `shouldReturn` Right flag it "Servant.API.Raw on success" $ \(_, baseUrl) -> do - res <- runExceptT (getRawSuccess HTTP.methodGet manager baseUrl) + res <- runClientM (getRawSuccess HTTP.methodGet) (ClientEnv manager baseUrl) case res of Left e -> assertFailure $ show e Right (code, body, ct, _, response) -> do @@ -286,7 +286,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do C.responseStatus response `shouldBe` HTTP.ok200 it "Servant.API.Raw should return a Left in case of failure" $ \(_, baseUrl) -> do - res <- runExceptT (getRawFailure HTTP.methodGet manager baseUrl) + res <- runClientM (getRawFailure HTTP.methodGet) (ClientEnv manager baseUrl) case res of Right _ -> assertFailure "expected Left, but got Right" Left e -> do @@ -294,7 +294,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do Servant.Client.responseBody e `shouldBe` "rawFailure" it "Returns headers appropriately" $ \(_, baseUrl) -> do - res <- runExceptT (getRespHeaders manager baseUrl) + res <- runClientM getRespHeaders (ClientEnv manager baseUrl) case res of Left e -> assertFailure $ show e Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")] @@ -303,7 +303,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \(_, baseUrl) -> property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body -> ioProperty $ do - result <- left show <$> runExceptT (getMultiple cap num flag body manager baseUrl) + result <- left show <$> runClientM (getMultiple cap num flag body) (ClientEnv manager baseUrl) return $ result === Right (cap, num, flag, body) @@ -315,9 +315,9 @@ wrappedApiSpec = describe "error status codes" $ do let test :: (WrappedApi, String) -> Spec test (WrappedApi api, desc) = it desc $ bracket (startWaiApp $ serveW api) endWaiApp $ \(_, baseUrl) -> do - let getResponse :: C.Manager -> BaseUrl -> SCR.ClientM () + let getResponse :: SCR.ClientM () getResponse = client api - Left FailureResponse{..} <- runExceptT (getResponse manager baseUrl) + Left FailureResponse{..} <- runClientM getResponse (ClientEnv manager baseUrl) responseStatus `shouldBe` (HTTP.Status 500 "error message") in mapM_ test $ (WrappedApi (Proxy :: Proxy (Delete '[JSON] ())), "Delete") : @@ -332,42 +332,42 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do context "client returns errors appropriately" $ do it "reports FailureResponse" $ \(_, baseUrl) -> do let (_ :<|> getDeleteEmpty :<|> _) = client api - Left res <- runExceptT (getDeleteEmpty manager baseUrl) + Left res <- runClientM getDeleteEmpty (ClientEnv manager baseUrl) case res of FailureResponse (HTTP.Status 404 "Not Found") _ _ -> return () _ -> fail $ "expected 404 response, but got " <> show res it "reports DecodeFailure" $ \(_, baseUrl) -> do let (_ :<|> _ :<|> getCapture :<|> _) = client api - Left res <- runExceptT (getCapture "foo" manager baseUrl) + Left res <- runClientM (getCapture "foo") (ClientEnv manager baseUrl) case res of DecodeFailure _ ("application/json") _ -> return () _ -> fail $ "expected DecodeFailure, but got " <> show res it "reports ConnectionError" $ \_ -> do let (getGetWrongHost :<|> _) = client api - Left res <- runExceptT (getGetWrongHost manager (BaseUrl Http "127.0.0.1" 19872 "")) + Left res <- runClientM getGetWrongHost (ClientEnv manager (BaseUrl Http "127.0.0.1" 19872 "")) case res of ConnectionError _ -> return () _ -> fail $ "expected ConnectionError, but got " <> show res it "reports UnsupportedContentType" $ \(_, baseUrl) -> do let (getGet :<|> _ ) = client api - Left res <- runExceptT (getGet manager baseUrl) + Left res <- runClientM getGet (ClientEnv manager baseUrl) case res of UnsupportedContentType ("application/octet-stream") _ -> return () _ -> fail $ "expected UnsupportedContentType, but got " <> show res it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api - Left res <- runExceptT (getBody alice manager baseUrl) + Left res <- runClientM (getBody alice) (ClientEnv manager baseUrl) case res of InvalidContentTypeHeader "fooooo" _ -> return () _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res data WrappedApi where WrappedApi :: (HasServer (api :: *) '[], Server api ~ Handler a, - HasClient api, Client api ~ (C.Manager -> BaseUrl -> SCR.ClientM ())) => + HasClient api, Client api ~ SCR.ClientM ()) => Proxy api -> WrappedApi basicAuthSpec :: Spec @@ -377,14 +377,14 @@ basicAuthSpec = beforeAll (startWaiApp basicAuthServer) $ afterAll endWaiApp $ d it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do let getBasic = client basicAuthAPI let basicAuthData = BasicAuthData "servant" "server" - (left show <$> runExceptT (getBasic basicAuthData manager baseUrl)) `shouldReturn` Right alice + (left show <$> runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)) `shouldReturn` Right alice context "Authentication is rejected when requests are not authenticated properly" $ do it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do let getBasic = client basicAuthAPI let basicAuthData = BasicAuthData "not" "password" - Left FailureResponse{..} <- runExceptT (getBasic basicAuthData manager baseUrl) + Left FailureResponse{..} <- runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl) responseStatus `shouldBe` HTTP.Status 403 "Forbidden" genAuthSpec :: Spec @@ -394,14 +394,14 @@ genAuthSpec = beforeAll (startWaiApp genAuthServer) $ afterAll endWaiApp $ do it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do let getProtected = client genAuthAPI let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "AuthHeader" ("cool" :: String) req) - (left show <$> runExceptT (getProtected authRequest manager baseUrl)) `shouldReturn` Right alice + (left show <$> runClientM (getProtected authRequest) (ClientEnv manager baseUrl)) `shouldReturn` Right alice context "Authentication is rejected when requests are not authenticated properly" $ do it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do let getProtected = client genAuthAPI let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "Wrong" ("header" :: String) req) - Left FailureResponse{..} <- runExceptT (getProtected authRequest manager baseUrl) + Left FailureResponse{..} <- runClientM (getProtected authRequest) (ClientEnv manager baseUrl) responseStatus `shouldBe` (HTTP.Status 401 "Unauthorized") -- * utils From 782f8e154156911d954d5f88f32ec850d73a825b Mon Sep 17 00:00:00 2001 From: Oleg Grenrus Date: Thu, 8 Sep 2016 17:36:10 +0300 Subject: [PATCH 36/43] Move servant-mock to https://github.com/haskell-servant/servant-mock --- servant-mock/.ghci | 1 - servant-mock/LICENSE | 30 ---- servant-mock/Setup.hs | 2 - servant-mock/example/main.hs | 26 --- servant-mock/include/overlapping-compat.h | 8 - servant-mock/servant-mock.cabal | 72 --------- servant-mock/src/Servant/Mock.hs | 188 ---------------------- servant-mock/test/Servant/MockSpec.hs | 86 ---------- servant-mock/test/Spec.hs | 1 - servant-mock/tinc.yaml | 5 - sources.txt | 1 - stack-ghc-7.8.4.yaml | 5 - stack-ghc-8.0.1.yaml | 1 - stack.yaml | 1 - 14 files changed, 427 deletions(-) delete mode 100644 servant-mock/.ghci delete mode 100644 servant-mock/LICENSE delete mode 100644 servant-mock/Setup.hs delete mode 100644 servant-mock/example/main.hs delete mode 100644 servant-mock/include/overlapping-compat.h delete mode 100644 servant-mock/servant-mock.cabal delete mode 100644 servant-mock/src/Servant/Mock.hs delete mode 100644 servant-mock/test/Servant/MockSpec.hs delete mode 100644 servant-mock/test/Spec.hs delete mode 100644 servant-mock/tinc.yaml diff --git a/servant-mock/.ghci b/servant-mock/.ghci deleted file mode 100644 index 0215492d..00000000 --- a/servant-mock/.ghci +++ /dev/null @@ -1 +0,0 @@ -:set -Wall -itest -isrc -optP-include -optPdist/build/autogen/cabal_macros.h -Iinclude diff --git a/servant-mock/LICENSE b/servant-mock/LICENSE deleted file mode 100644 index 68d30586..00000000 --- a/servant-mock/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -Copyright (c) 2015-2016, Servant Contributors - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of Alp Mestanogullari nor the names of other - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/servant-mock/Setup.hs b/servant-mock/Setup.hs deleted file mode 100644 index 44671092..00000000 --- a/servant-mock/Setup.hs +++ /dev/null @@ -1,2 +0,0 @@ -import Distribution.Simple -main = defaultMain diff --git a/servant-mock/example/main.hs b/servant-mock/example/main.hs deleted file mode 100644 index a602dc88..00000000 --- a/servant-mock/example/main.hs +++ /dev/null @@ -1,26 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE TypeOperators #-} - -{-# OPTIONS_GHC -fno-warn-unused-binds #-} - -import Data.Aeson -import GHC.Generics -import Network.Wai.Handler.Warp -import Servant -import Servant.Mock -import Test.QuickCheck.Arbitrary - -newtype User = User { username :: String } - deriving (Eq, Show, Arbitrary, Generic) - -instance ToJSON User - -type API = "user" :> Get '[JSON] User - -api :: Proxy API -api = Proxy - -main :: IO () -main = run 8080 (serve api $ mock api Proxy) diff --git a/servant-mock/include/overlapping-compat.h b/servant-mock/include/overlapping-compat.h deleted file mode 100644 index eef9d4ea..00000000 --- a/servant-mock/include/overlapping-compat.h +++ /dev/null @@ -1,8 +0,0 @@ -#if __GLASGOW_HASKELL__ >= 710 -#define OVERLAPPABLE_ {-# OVERLAPPABLE #-} -#define OVERLAPPING_ {-# OVERLAPPING #-} -#else -{-# LANGUAGE OverlappingInstances #-} -#define OVERLAPPABLE_ -#define OVERLAPPING_ -#endif diff --git a/servant-mock/servant-mock.cabal b/servant-mock/servant-mock.cabal deleted file mode 100644 index e7896482..00000000 --- a/servant-mock/servant-mock.cabal +++ /dev/null @@ -1,72 +0,0 @@ -name: servant-mock -version: 0.8.1 -synopsis: Derive a mock server for free from your servant API types -description: - Derive a mock server for free from your servant API types - . - See the @Servant.Mock@ module for the documentation and an example. -homepage: http://github.com/haskell-servant/servant -license: BSD3 -license-file: LICENSE -author: Servant Contributors -maintainer: haskell-servant-maintainers@googlegroups.com -copyright: 2015-2016 Servant Contributors -category: Web -build-type: Simple -extra-source-files: include/*.h -cabal-version: >=1.10 -bug-reports: http://github.com/haskell-servant/servant/issues -source-repository head - type: git - location: http://github.com/haskell-servant/servant.git - -flag example - description: Build the example too - default: True - -library - exposed-modules: - Servant.Mock - build-depends: - base >=4.7 && <5, - bytestring >= 0.10 && <0.11, - http-types >= 0.8 && <0.10, - servant == 0.8.*, - servant-server == 0.8.*, - transformers >= 0.3 && <0.6, - QuickCheck >= 2.7 && <2.10, - wai >= 3.0 && <3.3 - hs-source-dirs: src - default-language: Haskell2010 - include-dirs: include - ghc-options: -Wall - -executable mock-app - main-is: main.hs - hs-source-dirs: example - default-language: Haskell2010 - build-depends: aeson, base, servant-mock, servant-server, QuickCheck, warp - if flag(example) - buildable: True - else - buildable: False - ghc-options: -Wall - -test-suite spec - type: exitcode-stdio-1.0 - ghc-options: -Wall - default-language: Haskell2010 - hs-source-dirs: test - main-is: Spec.hs - other-modules: - Servant.MockSpec - build-depends: - base, - hspec, - hspec-wai, - QuickCheck, - servant, - servant-server, - servant-mock, - aeson, - wai diff --git a/servant-mock/src/Servant/Mock.hs b/servant-mock/src/Servant/Mock.hs deleted file mode 100644 index 0d0f4a48..00000000 --- a/servant-mock/src/Servant/Mock.hs +++ /dev/null @@ -1,188 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} - -#include "overlapping-compat.h" - --- | --- Module : Servant.Mock --- Copyright : 2015 Alp Mestanogullari --- License : BSD3 --- --- Maintainer : Alp Mestanogullari --- Stability : experimental --- Portability : portable --- --- Automatically derive a mock webserver that implements some API type, --- just from the said API type's definition. --- --- Using this module couldn't be simpler. Given some API type, like: --- --- > type API = "user" :> Get '[JSON] User --- --- that describes your web application, all you have to do is define --- a 'Proxy' to it: --- --- > myAPI :: Proxy API --- > myAPI = Proxy --- --- and call 'mock', which has the following type: --- --- @ --- 'mock' :: 'HasMock' api context => 'Proxy' api -> 'Proxy' context -> 'Server' api --- @ --- --- What this says is, given some API type @api@ that it knows it can --- "mock", 'mock' hands you an implementation of the API type. It does so --- by having each request handler generate a random value of the --- appropriate type (@User@ in our case). All you need for this to work is --- to provide 'Arbitrary' instances for the data types returned as response --- bodies, hence appearing next to 'Delete', 'Get', 'Patch', 'Post' and 'Put'. --- --- To put this all to work and run the mock server, just call 'serve' on the --- result of 'mock' to get an 'Application' that you can then run with warp. --- --- @ --- main :: IO () --- main = Network.Wai.Handler.Warp.run 8080 $ --- 'serve' myAPI ('mock' myAPI Proxy) --- @ -module Servant.Mock ( HasMock(..) ) where - -#if !MIN_VERSION_base(4,8,0) -import Control.Applicative -#endif -import Control.Monad.IO.Class -import Data.ByteString.Lazy.Char8 (pack) -import Data.Proxy -import GHC.TypeLits -import Network.HTTP.Types.Status -import Network.Wai -import Servant -import Servant.API.ContentTypes -import Test.QuickCheck.Arbitrary (Arbitrary (..), vector) -import Test.QuickCheck.Gen (Gen, generate) - --- | 'HasMock' defines an interpretation of API types --- than turns them into random-response-generating --- request handlers, hence providing an instance for --- all the combinators of the core /servant/ library. -class HasServer api context => HasMock api context where - -- | Calling this method creates request handlers of - -- the right type to implement the API described by - -- @api@ that just generate random response values of - -- the right type. E.g: - -- - -- @ - -- type API = "user" :> Get '[JSON] User - -- :<|> "book" :> Get '[JSON] Book - -- - -- api :: Proxy API - -- api = Proxy - -- - -- -- let's say we will start with the frontend, - -- -- and hence need a placeholder server - -- server :: Server API - -- server = mock api Proxy - -- @ - -- - -- What happens here is that @'Server' API@ - -- actually "means" 2 request handlers, of the following types: - -- - -- @ - -- getUser :: Handler User - -- getBook :: Handler Book - -- @ - -- - -- So under the hood, 'mock' uses the 'IO' bit to generate - -- random values of type 'User' and 'Book' every time these - -- endpoints are requested. - mock :: Proxy api -> Proxy context -> Server api - -instance (HasMock a context, HasMock b context) => HasMock (a :<|> b) context where - mock _ context = mock (Proxy :: Proxy a) context :<|> mock (Proxy :: Proxy b) context - -instance (KnownSymbol path, HasMock rest context) => HasMock (path :> rest) context where - mock _ = mock (Proxy :: Proxy rest) - -instance (KnownSymbol s, FromHttpApiData a, HasMock rest context) => HasMock (Capture s a :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance (KnownSymbol s, FromHttpApiData a, HasMock rest context) => HasMock (CaptureAll s a :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance (AllCTUnrender ctypes a, HasMock rest context) => HasMock (ReqBody ctypes a :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance HasMock rest context => HasMock (RemoteHost :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance HasMock rest context => HasMock (IsSecure :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance HasMock rest context => HasMock (Vault :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance HasMock rest context => HasMock (HttpVersion :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance (KnownSymbol s, FromHttpApiData a, HasMock rest context) - => HasMock (QueryParam s a :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance (KnownSymbol s, FromHttpApiData a, HasMock rest context) - => HasMock (QueryParams s a :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance (KnownSymbol s, HasMock rest context) => HasMock (QueryFlag s :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance (KnownSymbol h, FromHttpApiData a, HasMock rest context) => HasMock (Header h a :> rest) context where - mock _ context = \_ -> mock (Proxy :: Proxy rest) context - -instance (Arbitrary a, KnownNat status, ReflectMethod method, AllCTRender ctypes a) - => HasMock (Verb method status ctypes a) context where - mock _ _ = mockArbitrary - -instance OVERLAPPING_ - (GetHeaders (Headers headerTypes a), Arbitrary (HList headerTypes), - Arbitrary a, KnownNat status, ReflectMethod method, AllCTRender ctypes a) - => HasMock (Verb method status ctypes (Headers headerTypes a)) context where - mock _ _ = mockArbitrary - -instance HasMock Raw context where - mock _ _ = \_req respond -> do - bdy <- genBody - respond $ responseLBS status200 [] bdy - - where genBody = pack <$> generate (vector 100 :: Gen [Char]) - -instance (HasContextEntry context (NamedContext name subContext), HasMock rest subContext) => - HasMock (WithNamedContext name subContext rest) context where - - mock _ _ = mock (Proxy :: Proxy rest) (Proxy :: Proxy subContext) - -mockArbitrary :: (MonadIO m, Arbitrary a) => m a -mockArbitrary = liftIO (generate arbitrary) - --- utility instance -instance (Arbitrary (HList ls), Arbitrary a) - => Arbitrary (Headers ls a) where - arbitrary = Headers <$> arbitrary <*> arbitrary - -instance Arbitrary (HList '[]) where - arbitrary = pure HNil - -instance (Arbitrary a, Arbitrary (HList hs)) - => Arbitrary (HList (Header h a ': hs)) where - arbitrary = HCons <$> fmap Header arbitrary <*> arbitrary - -instance Arbitrary NoContent where - arbitrary = pure NoContent diff --git a/servant-mock/test/Servant/MockSpec.hs b/servant-mock/test/Servant/MockSpec.hs deleted file mode 100644 index 83401c73..00000000 --- a/servant-mock/test/Servant/MockSpec.hs +++ /dev/null @@ -1,86 +0,0 @@ -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeOperators #-} - -module Servant.MockSpec where - -import Data.Aeson as Aeson -import Data.Proxy -import GHC.Generics -import Network.Wai -import Servant.API -import Test.Hspec hiding (pending) -import Test.Hspec.Wai -import Test.QuickCheck - -import Servant -import Servant.API.Internal.Test.ComprehensiveAPI -import Servant.Mock - --- This declaration simply checks that all instances are in place. -_ = mock comprehensiveAPI (Proxy :: Proxy '[NamedContext "foo" '[]]) - -data Body - = Body - | ArbitraryBody - deriving (Generic) - -instance ToJSON Body - -instance Arbitrary Body where - arbitrary = return ArbitraryBody - -data TestHeader - = TestHeader - | ArbitraryHeader - deriving (Show) - -instance ToHttpApiData TestHeader where - toHeader = toHeader . show - toUrlPiece = toUrlPiece . show - toQueryParam = toQueryParam . show - - -instance Arbitrary TestHeader where - arbitrary = return ArbitraryHeader - -spec :: Spec -spec = do - describe "mock" $ do - context "Get" $ do - let api :: Proxy (Get '[JSON] Body) - api = Proxy - app = serve api (mock api Proxy) - with (return app) $ do - it "serves arbitrary response bodies" $ do - get "/" `shouldRespondWith` 200{ - matchBody = Just $ Aeson.encode ArbitraryBody - } - - context "response headers" $ do - let withHeader :: Proxy (Get '[JSON] (Headers '[Header "foo" TestHeader] Body)) - withHeader = Proxy - withoutHeader :: Proxy (Get '[JSON] (Headers '[] Body)) - withoutHeader = Proxy - toApp :: (HasMock api '[]) => Proxy api -> IO Application - toApp api = return $ serve api (mock api (Proxy :: Proxy '[])) - with (toApp withHeader) $ do - it "serves arbitrary response bodies" $ do - get "/" `shouldRespondWith` 200{ - matchHeaders = return $ MatchHeader $ \ h -> - if h == [("Content-Type", "application/json"), ("foo", "ArbitraryHeader")] - then Nothing - else Just ("headers not correct\n") - } - - with (toApp withoutHeader) $ do - it "works for no additional headers" $ do - get "/" `shouldRespondWith` 200{ - matchHeaders = return $ MatchHeader $ \ h -> - if h == [("Content-Type", "application/json")] - then Nothing - else Just ("headers not correct\n") - } diff --git a/servant-mock/test/Spec.hs b/servant-mock/test/Spec.hs deleted file mode 100644 index a824f8c3..00000000 --- a/servant-mock/test/Spec.hs +++ /dev/null @@ -1 +0,0 @@ -{-# OPTIONS_GHC -F -pgmF hspec-discover #-} diff --git a/servant-mock/tinc.yaml b/servant-mock/tinc.yaml deleted file mode 100644 index ec6d448f..00000000 --- a/servant-mock/tinc.yaml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: - - name: servant - path: ../servant - - name: servant-server - path: ../servant-server diff --git a/sources.txt b/sources.txt index 06ff7ed8..6c75de5e 100644 --- a/sources.txt +++ b/sources.txt @@ -4,4 +4,3 @@ servant-client servant-docs servant-foreign servant-js -servant-mock diff --git a/stack-ghc-7.8.4.yaml b/stack-ghc-7.8.4.yaml index 0fe58482..c934dc41 100644 --- a/stack-ghc-7.8.4.yaml +++ b/stack-ghc-7.8.4.yaml @@ -5,7 +5,6 @@ packages: - servant-docs/ - servant-foreign/ - servant-js/ -- servant-mock/ - servant-server/ extra-deps: - base-compat-0.9.1 @@ -18,10 +17,6 @@ extra-deps: - hspec-expectations-0.7.2 - http-api-data-0.2.2 - primitive-0.6.1.0 -- servant-0.7.1 -- servant-client-0.7.1 -- servant-docs-0.7.1 -- servant-server-0.7.1 - should-not-typecheck-2.1.0 - time-locale-compat-0.1.1.1 - wai-app-static-3.1.5 diff --git a/stack-ghc-8.0.1.yaml b/stack-ghc-8.0.1.yaml index 8861e1a9..c5c8fa27 100644 --- a/stack-ghc-8.0.1.yaml +++ b/stack-ghc-8.0.1.yaml @@ -5,7 +5,6 @@ packages: - servant-docs/ - servant-foreign/ - servant-js/ -- servant-mock/ - servant-server/ extra-deps: [] flags: {} diff --git a/stack.yaml b/stack.yaml index 95599455..a1e5f8c9 100644 --- a/stack.yaml +++ b/stack.yaml @@ -5,7 +5,6 @@ packages: - servant-docs/ - servant-foreign/ - servant-js/ -- servant-mock/ - servant-server/ - doc/tutorial extra-deps: From f44b336bf10e9f5cb6d27a8c506e8b7cf01f33b1 Mon Sep 17 00:00:00 2001 From: Oleg Grenrus Date: Wed, 31 Aug 2016 13:32:12 +0300 Subject: [PATCH 37/43] Support http-api-data-0.3 --- servant-client/servant-client.cabal | 2 +- servant-server/servant-server.cabal | 2 +- servant-server/src/Servant/Server/Internal.hs | 7 +++++++ servant/servant.cabal | 2 +- stack.yaml | 1 + 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index c49cfe0e..03155d96 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -41,7 +41,7 @@ library , base64-bytestring >= 1.0.0.1 && < 1.1 , bytestring >= 0.10 && < 0.11 , exceptions >= 0.8 && < 0.9 - , http-api-data >= 0.1 && < 0.3 + , http-api-data >= 0.1 && < 0.4 , http-client >= 0.4.18.1 && < 0.6 , http-client-tls >= 0.2.2 && < 0.4 , http-media >= 0.6.2 && < 0.7 diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index 29f3d8d3..284e7416 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -52,7 +52,7 @@ library , base64-bytestring >= 1.0 && < 1.1 , bytestring >= 0.10 && < 0.11 , containers >= 0.5 && < 0.6 - , http-api-data >= 0.1 && < 0.3 + , http-api-data >= 0.1 && < 0.4 , http-types >= 0.8 && < 0.10 , network-uri >= 2.6 && < 2.7 , mtl >= 2 && < 2.3 diff --git a/servant-server/src/Servant/Server/Internal.hs b/servant-server/src/Servant/Server/Internal.hs index de4a237a..2c94ef1c 100644 --- a/servant-server/src/Servant/Server/Internal.hs +++ b/servant-server/src/Servant/Server/Internal.hs @@ -43,10 +43,17 @@ import Network.Wai (Application, Request, Response, import Prelude () import Prelude.Compat import Web.HttpApiData (FromHttpApiData) +#if MIN_VERSION_http_api_data(0,3,0) +import Web.Internal.HttpApiData (parseHeaderMaybe, + parseQueryParamMaybe, + parseUrlPieceMaybe, + parseUrlPieces) +#else import Web.HttpApiData.Internal (parseHeaderMaybe, parseQueryParamMaybe, parseUrlPieceMaybe, parseUrlPieces) +#endif import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture, CaptureAll, Verb, diff --git a/servant/servant.cabal b/servant/servant.cabal index 56a2cc6e..fb1cef5e 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -55,7 +55,7 @@ library , attoparsec >= 0.12 && < 0.14 , bytestring >= 0.10 && < 0.11 , case-insensitive >= 1.2 && < 1.3 - , http-api-data >= 0.1 && < 0.3 + , http-api-data >= 0.1 && < 0.4 , http-media >= 0.4 && < 0.7 , http-types >= 0.8 && < 0.10 , mtl >= 2.0 && < 2.3 diff --git a/stack.yaml b/stack.yaml index a1e5f8c9..70a45e22 100644 --- a/stack.yaml +++ b/stack.yaml @@ -8,4 +8,5 @@ packages: - servant-server/ - doc/tutorial extra-deps: +- http-api-data-0.3 resolver: lts-6.0 From 0870b3b2f55166237a4382481c7fe306f33ba733 Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Thu, 1 Sep 2016 21:24:49 -0300 Subject: [PATCH 38/43] Remove FromFormUrlEncoded and ToFormUrlEncoded. In favor of FromForm and ToForm from the new version of http-api-data. --- servant-client/servant-client.cabal | 1 + servant-client/test/Servant/ClientSpec.hs | 26 +++----- servant/CHANGELOG.md | 3 +- servant/src/Servant/API.hs | 4 +- servant/src/Servant/API/ContentTypes.hs | 67 ++++---------------- servant/test/Servant/API/ContentTypesSpec.hs | 17 ----- 6 files changed, 24 insertions(+), 94 deletions(-) diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index 03155d96..c0cf8bb9 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -78,6 +78,7 @@ test-suite spec , bytestring , deepseq , hspec == 2.* + , http-api-data , http-client , http-media , http-types diff --git a/servant-client/test/Servant/ClientSpec.hs b/servant-client/test/Servant/ClientSpec.hs index 1f0be75b..c3b957d4 100644 --- a/servant-client/test/Servant/ClientSpec.hs +++ b/servant-client/test/Servant/ClientSpec.hs @@ -30,7 +30,7 @@ module Servant.ClientSpec where import Control.Applicative ((<$>)) #endif import Control.Arrow (left) -import Control.Concurrent (forkIO, killThread, ThreadId) +import Control.Concurrent (ThreadId, forkIO, killThread) import Control.Exception (bracket) import Control.Monad.Trans.Except (throwE ) import Data.Aeson @@ -39,19 +39,20 @@ import Data.Char (chr, isPrint) import Data.Foldable (forM_) import Data.Monoid hiding (getLast) import Data.Proxy -import qualified Data.Text as T import GHC.Generics (Generic) import qualified Network.HTTP.Client as C import Network.HTTP.Media import qualified Network.HTTP.Types as HTTP import Network.Socket -import Network.Wai (Request, requestHeaders, responseLBS) +import Network.Wai (Request, requestHeaders, + responseLBS) import Network.Wai.Handler.Warp import System.IO.Unsafe (unsafePerformIO) import Test.Hspec import Test.Hspec.QuickCheck import Test.HUnit import Test.QuickCheck +import Web.FormUrlEncoded (FromForm, ToForm) import Servant.API import Servant.API.Internal.Test.ComprehensiveAPI @@ -82,19 +83,8 @@ data Person = Person { instance ToJSON Person instance FromJSON Person -instance ToFormUrlEncoded Person where - toFormUrlEncoded Person{..} = - [("name", T.pack name), ("age", T.pack (show age))] - -lookupEither :: (Show a, Eq a) => a -> [(a,b)] -> Either String b -lookupEither x xs = do - maybe (Left $ "could not find key " <> show x) return $ lookup x xs - -instance FromFormUrlEncoded Person where - fromFormUrlEncoded xs = do - n <- lookupEither "name" xs - a <- lookupEither "age" xs - return $ Person (T.unpack n) (read $ T.unpack a) +instance ToForm Person where +instance FromForm Person where alice :: Person alice = Person "Alice" 42 @@ -131,9 +121,9 @@ getBody :: Person -> SCR.ClientM Person getQueryParam :: Maybe String -> SCR.ClientM Person getQueryParams :: [String] -> SCR.ClientM [Person] getQueryFlag :: Bool -> SCR.ClientM Bool -getRawSuccess :: HTTP.Method +getRawSuccess :: HTTP.Method -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString) -getRawFailure :: HTTP.Method +getRawFailure :: HTTP.Method -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString) getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])] -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])]) diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index 41e73639..d07c9477 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -1,12 +1,13 @@ next ---- * Added Eq, Show, Read, Generic and Ord instances to IsSecure +* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo` +* BACKWARD INCOMPATIBLE: Moved `From/ToFormUrlEncoded` classes, which were renamed to `From/ToForm` to `http-api-data` 0.8.1 ---- * Add `CaptureAll` combinator. Captures all of the remaining segments in a URL. -* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo` 0.8 --- diff --git a/servant/src/Servant/API.hs b/servant/src/Servant/API.hs index cbb0db09..2eb0d8dc 100644 --- a/servant/src/Servant/API.hs +++ b/servant/src/Servant/API.hs @@ -62,10 +62,10 @@ import Servant.API.Alternative ((:<|>) (..)) import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..)) import Servant.API.Capture (Capture, CaptureAll) import Servant.API.ContentTypes (Accept (..), FormUrlEncoded, - FromFormUrlEncoded (..), JSON, + JSON, MimeRender (..), NoContent (NoContent), MimeUnrender (..), OctetStream, - PlainText, ToFormUrlEncoded (..)) + PlainText) import Servant.API.Experimental.Auth (AuthProtect) import Servant.API.Header (Header (..)) import Servant.API.HttpVersion (HttpVersion (..)) diff --git a/servant/src/Servant/API/ContentTypes.hs b/servant/src/Servant/API/ContentTypes.hs index f10e2ba1..6969f7ee 100644 --- a/servant/src/Servant/API/ContentTypes.hs +++ b/servant/src/Servant/API/ContentTypes.hs @@ -66,15 +66,14 @@ module Servant.API.ContentTypes , AllMime(..) , AllMimeRender(..) , AllMimeUnrender(..) - , FromFormUrlEncoded(..) - , ToFormUrlEncoded(..) , eitherDecodeLenient , canHandleAcceptH ) where import Control.Arrow (left) import Control.Monad.Compat -import Data.Aeson (FromJSON(..), ToJSON(..), encode) +import Data.Aeson (FromJSON (..), ToJSON (..), + encode) import Data.Aeson.Parser (value) import Data.Aeson.Types (parseEither) import Data.Attoparsec.ByteString.Char8 (endOfInput, parseOnly, @@ -82,10 +81,8 @@ import Data.Attoparsec.ByteString.Char8 (endOfInput, parseOnly, import qualified Data.ByteString as BS import Data.ByteString.Lazy (ByteString, fromStrict, toStrict) -import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Lazy.Char8 as BC import Data.Maybe (isJust) -import Data.Monoid.Compat import Data.String.Conversions (cs) import qualified Data.Text as TextS import qualified Data.Text.Encoding as TextS @@ -94,8 +91,9 @@ import qualified Data.Text.Lazy.Encoding as TextL import Data.Typeable import GHC.Generics (Generic) import qualified Network.HTTP.Media as M -import Network.URI (escapeURIString, - isUnreserved, unEscapeString) +import Web.FormUrlEncoded (FromForm, ToForm, + urlEncodeAsForm, + urlDecodeAsForm) import Prelude () import Prelude.Compat @@ -290,12 +288,12 @@ instance OVERLAPPABLE_ ToJSON a => MimeRender JSON a where mimeRender _ = encode --- | @encodeFormUrlEncoded . toFormUrlEncoded@ +-- | @urlEncodeAsForm" -- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only -- holds if every element of x is non-null (i.e., not @("", "")@) instance OVERLAPPABLE_ - ToFormUrlEncoded a => MimeRender FormUrlEncoded a where - mimeRender _ = encodeFormUrlEncoded . toFormUrlEncoded + ToForm a => MimeRender FormUrlEncoded a where + mimeRender _ = urlEncodeAsForm -- | `TextL.encodeUtf8` instance MimeRender PlainText TextL.Text where @@ -348,11 +346,11 @@ eitherDecodeLenient input = instance FromJSON a => MimeUnrender JSON a where mimeUnrender _ = eitherDecodeLenient --- | @decodeFormUrlEncoded >=> fromFormUrlEncoded@ +-- | @urlDecodeAsForm@ -- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only -- holds if every element of x is non-null (i.e., not @("", "")@) -instance FromFormUrlEncoded a => MimeUnrender FormUrlEncoded a where - mimeUnrender _ = decodeFormUrlEncoded >=> fromFormUrlEncoded +instance FromForm a => MimeUnrender FormUrlEncoded a where + mimeUnrender _ = left TextS.unpack . urlDecodeAsForm -- | @left show . TextL.decodeUtf8'@ instance MimeUnrender PlainText TextL.Text where @@ -375,49 +373,6 @@ instance MimeUnrender OctetStream BS.ByteString where mimeUnrender _ = Right . toStrict --------------------------------------------------------------------------- --- * FormUrlEncoded - --- | A type that can be converted to @application/x-www-form-urlencoded@ -class ToFormUrlEncoded a where - toFormUrlEncoded :: a -> [(TextS.Text, TextS.Text)] - -instance ToFormUrlEncoded [(TextS.Text, TextS.Text)] where - toFormUrlEncoded = id - --- | A type that can be converted from @application/x-www-form-urlencoded@, --- with the possibility of failure. -class FromFormUrlEncoded a where - fromFormUrlEncoded :: [(TextS.Text, TextS.Text)] -> Either String a - -instance FromFormUrlEncoded [(TextS.Text, TextS.Text)] where - fromFormUrlEncoded = return - -encodeFormUrlEncoded :: [(TextS.Text, TextS.Text)] -> ByteString -encodeFormUrlEncoded xs = - let escape :: TextS.Text -> ByteString - escape = cs . escapeURIString isUnreserved . cs - encodePair :: (TextS.Text, TextS.Text) -> ByteString - encodePair (k, "") = escape k - encodePair (k, v) = escape k <> "=" <> escape v - in B.intercalate "&" $ map encodePair xs - -decodeFormUrlEncoded :: ByteString -> Either String [(TextS.Text, TextS.Text)] -decodeFormUrlEncoded "" = return [] -decodeFormUrlEncoded q = do - let xs :: [TextS.Text] - xs = TextS.splitOn "&" . cs $ q - parsePair :: TextS.Text -> Either String (TextS.Text, TextS.Text) - parsePair p = - case TextS.splitOn "=" p of - [k,v] -> return ( unescape k - , unescape v - ) - [k] -> return ( unescape k, "" ) - _ -> Left $ "not a valid pair: " <> cs p - unescape :: TextS.Text -> TextS.Text - unescape = cs . unEscapeString . cs . TextS.intercalate "%20" . TextS.splitOn "+" - mapM parsePair xs -- $setup -- >>> import Servant.API diff --git a/servant/test/Servant/API/ContentTypesSpec.hs b/servant/test/Servant/API/ContentTypesSpec.hs index 1a155b5c..a0ae13d7 100644 --- a/servant/test/Servant/API/ContentTypesSpec.hs +++ b/servant/test/Servant/API/ContentTypesSpec.hs @@ -11,7 +11,6 @@ module Servant.API.ContentTypesSpec where import Prelude () import Prelude.Compat -import Control.Arrow import Data.Aeson import Data.ByteString.Char8 (ByteString, append, pack) import qualified Data.ByteString.Lazy as BSL @@ -25,7 +24,6 @@ import Data.String.Conversions (cs) import qualified Data.Text as TextS import qualified Data.Text.Lazy as TextL import GHC.Generics -import Network.URL (exportParams, importParams) import Test.Hspec import Test.QuickCheck import "quickcheck-instances" Test.QuickCheck.Instances () @@ -68,21 +66,6 @@ spec = describe "Servant.API.ContentTypes" $ do it "has mimeUnrender reverse mimeRender for valid top-level json " $ do property $ \x -> mimeUnrender p (mimeRender p x) == Right (x::SomeData) - describe "The FormUrlEncoded Content-Type type" $ do - let p = Proxy :: Proxy FormUrlEncoded - - it "has mimeUnrender reverse mimeRender" $ do - property $ \x -> mempty `notElem` x - ==> mimeUnrender p (mimeRender p x) == Right (x::[(TextS.Text,TextS.Text)]) - - it "has mimeUnrender reverse exportParams (Network.URL)" $ do - property $ \x -> mempty `notElem` x - ==> (mimeUnrender p . cs . exportParams . map (cs *** cs) $ x) == Right (x::[(TextS.Text,TextS.Text)]) - - it "has importParams (Network.URL) reverse mimeRender" $ do - property $ \x -> mempty `notElem` x - ==> (fmap (map (cs *** cs)) . importParams . cs . mimeRender p $ x) == Just (x::[(TextS.Text,TextS.Text)]) - describe "The PlainText Content-Type type" $ do let p = Proxy :: Proxy PlainText From 7cdebce96b6793405e935bf177159cefb13b4c82 Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Thu, 1 Sep 2016 21:45:05 -0300 Subject: [PATCH 39/43] Bounds and stack files --- servant/servant.cabal | 2 +- stack-ghc-7.8.4.yaml | 3 ++- stack-ghc-8.0.1.yaml | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/servant/servant.cabal b/servant/servant.cabal index fb1cef5e..6d3030bf 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -55,7 +55,7 @@ library , attoparsec >= 0.12 && < 0.14 , bytestring >= 0.10 && < 0.11 , case-insensitive >= 1.2 && < 1.3 - , http-api-data >= 0.1 && < 0.4 + , http-api-data >= 0.3 && < 0.4 , http-media >= 0.4 && < 0.7 , http-types >= 0.8 && < 0.10 , mtl >= 2.0 && < 2.3 diff --git a/stack-ghc-7.8.4.yaml b/stack-ghc-7.8.4.yaml index c934dc41..15c1245a 100644 --- a/stack-ghc-7.8.4.yaml +++ b/stack-ghc-7.8.4.yaml @@ -15,9 +15,10 @@ extra-deps: - hspec-core-2.2.3 - hspec-discover-2.2.3 - hspec-expectations-0.7.2 -- http-api-data-0.2.2 +- http-api-data-0.3 - primitive-0.6.1.0 - should-not-typecheck-2.1.0 - time-locale-compat-0.1.1.1 +- uri-bytestring-0.2.2.0 - wai-app-static-3.1.5 resolver: lts-2.22 diff --git a/stack-ghc-8.0.1.yaml b/stack-ghc-8.0.1.yaml index c5c8fa27..21506764 100644 --- a/stack-ghc-8.0.1.yaml +++ b/stack-ghc-8.0.1.yaml @@ -6,5 +6,7 @@ packages: - servant-foreign/ - servant-js/ - servant-server/ -extra-deps: [] +extra-deps: +- http-api-data-0.3 +- uri-bytestring-0.2.2.0 flags: {} From 501212e6b9b141691090161901d46661a4706cf3 Mon Sep 17 00:00:00 2001 From: "Julian K. Arni" Date: Fri, 2 Sep 2016 10:25:22 -0300 Subject: [PATCH 40/43] Review fixes --- servant-client/servant-client.cabal | 2 +- servant-client/test/Servant/ClientSpec.hs | 9 ++++----- servant-server/servant-server.cabal | 2 +- servant-server/src/Servant/Server/Internal.hs | 11 +---------- servant/src/Servant/API/ContentTypes.hs | 5 ++--- 5 files changed, 9 insertions(+), 20 deletions(-) diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index c0cf8bb9..327f3d2d 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -41,7 +41,7 @@ library , base64-bytestring >= 1.0.0.1 && < 1.1 , bytestring >= 0.10 && < 0.11 , exceptions >= 0.8 && < 0.9 - , http-api-data >= 0.1 && < 0.4 + , http-api-data >= 0.3 && < 0.4 , http-client >= 0.4.18.1 && < 0.6 , http-client-tls >= 0.2.2 && < 0.4 , http-media >= 0.6.2 && < 0.7 diff --git a/servant-client/test/Servant/ClientSpec.hs b/servant-client/test/Servant/ClientSpec.hs index c3b957d4..98d9e9b5 100644 --- a/servant-client/test/Servant/ClientSpec.hs +++ b/servant-client/test/Servant/ClientSpec.hs @@ -30,7 +30,7 @@ module Servant.ClientSpec where import Control.Applicative ((<$>)) #endif import Control.Arrow (left) -import Control.Concurrent (ThreadId, forkIO, killThread) +import Control.Concurrent (forkIO, killThread, ThreadId) import Control.Exception (bracket) import Control.Monad.Trans.Except (throwE ) import Data.Aeson @@ -44,8 +44,7 @@ import qualified Network.HTTP.Client as C import Network.HTTP.Media import qualified Network.HTTP.Types as HTTP import Network.Socket -import Network.Wai (Request, requestHeaders, - responseLBS) +import Network.Wai (Request, requestHeaders, responseLBS) import Network.Wai.Handler.Warp import System.IO.Unsafe (unsafePerformIO) import Test.Hspec @@ -121,9 +120,9 @@ getBody :: Person -> SCR.ClientM Person getQueryParam :: Maybe String -> SCR.ClientM Person getQueryParams :: [String] -> SCR.ClientM [Person] getQueryFlag :: Bool -> SCR.ClientM Bool -getRawSuccess :: HTTP.Method +getRawSuccess :: HTTP.Method -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString) -getRawFailure :: HTTP.Method +getRawFailure :: HTTP.Method -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString) getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])] -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])]) diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index 284e7416..f9b5f45f 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -52,7 +52,7 @@ library , base64-bytestring >= 1.0 && < 1.1 , bytestring >= 0.10 && < 0.11 , containers >= 0.5 && < 0.6 - , http-api-data >= 0.1 && < 0.4 + , http-api-data >= 0.3 && < 0.4 , http-types >= 0.8 && < 0.10 , network-uri >= 2.6 && < 2.7 , mtl >= 2 && < 2.3 diff --git a/servant-server/src/Servant/Server/Internal.hs b/servant-server/src/Servant/Server/Internal.hs index 2c94ef1c..fc91267b 100644 --- a/servant-server/src/Servant/Server/Internal.hs +++ b/servant-server/src/Servant/Server/Internal.hs @@ -42,19 +42,10 @@ import Network.Wai (Application, Request, Response, responseLBS, vault) import Prelude () import Prelude.Compat -import Web.HttpApiData (FromHttpApiData) -#if MIN_VERSION_http_api_data(0,3,0) -import Web.Internal.HttpApiData (parseHeaderMaybe, +import Web.HttpApiData (FromHttpApiData, parseHeaderMaybe, parseQueryParamMaybe, parseUrlPieceMaybe, parseUrlPieces) -#else -import Web.HttpApiData.Internal (parseHeaderMaybe, - parseQueryParamMaybe, - parseUrlPieceMaybe, - parseUrlPieces) -#endif - import Servant.API ((:<|>) (..), (:>), BasicAuth, Capture, CaptureAll, Verb, ReflectMethod(reflectMethod), diff --git a/servant/src/Servant/API/ContentTypes.hs b/servant/src/Servant/API/ContentTypes.hs index 6969f7ee..044f1c59 100644 --- a/servant/src/Servant/API/ContentTypes.hs +++ b/servant/src/Servant/API/ContentTypes.hs @@ -72,8 +72,7 @@ module Servant.API.ContentTypes import Control.Arrow (left) import Control.Monad.Compat -import Data.Aeson (FromJSON (..), ToJSON (..), - encode) +import Data.Aeson (FromJSON(..), ToJSON(..), encode) import Data.Aeson.Parser (value) import Data.Aeson.Types (parseEither) import Data.Attoparsec.ByteString.Char8 (endOfInput, parseOnly, @@ -288,7 +287,7 @@ instance OVERLAPPABLE_ ToJSON a => MimeRender JSON a where mimeRender _ = encode --- | @urlEncodeAsForm" +-- | @urlEncodeAsForm@ -- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only -- holds if every element of x is non-null (i.e., not @("", "")@) instance OVERLAPPABLE_ From 5d4b730b3e198e7c4a2690b5f0862172c84ca9ed Mon Sep 17 00:00:00 2001 From: Tim Habermaas Date: Fri, 16 Sep 2016 18:48:54 +0200 Subject: [PATCH 41/43] Fix typo in servant-js README --- servant-js/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servant-js/README.md b/servant-js/README.md index e92f9f2d..f2c99616 100644 --- a/servant-js/README.md +++ b/servant-js/README.md @@ -78,8 +78,8 @@ server counter = counterPlusOne counter -- (+1) on the TVar :<|> currentValue counter -- read the TVar server' :: TVar Counter -> Server TestApi' -server counter = server counter - :<|> serveDirectory www -- serve static files +server' counter = server counter + :<|> serveDirectory www -- serve static files runServer :: TVar Counter -- ^ shared variable for the counter -> Int -- ^ port the server should listen on From d7d22b8050d40028aa8733187de0c13aa9497ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Sat, 17 Sep 2016 11:46:26 -0400 Subject: [PATCH 42/43] tweake changelogs for the upcoming release --- servant-client/CHANGELOG.md | 5 ++--- servant/CHANGELOG.md | 9 +++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/servant-client/CHANGELOG.md b/servant-client/CHANGELOG.md index df037a86..02a6ef39 100644 --- a/servant-client/CHANGELOG.md +++ b/servant-client/CHANGELOG.md @@ -1,11 +1,10 @@ -0.8.1 ------ +0.9 +--- * BACKWARDS INCOMPATIBLE: `client` now returns a ClientM which is a Reader for BasicEnv. BasicEnv comprises the HttpManager and BaseUrl that have had to be passed to each method returned by `client`. - 0.7.1 ----- diff --git a/servant/CHANGELOG.md b/servant/CHANGELOG.md index d07c9477..15a5532a 100644 --- a/servant/CHANGELOG.md +++ b/servant/CHANGELOG.md @@ -1,8 +1,9 @@ -next ----- +0.9 +--- + * Added Eq, Show, Read, Generic and Ord instances to IsSecure -* BACKWARDS INCOMPATIBLE replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo` -* BACKWARD INCOMPATIBLE: Moved `From/ToFormUrlEncoded` classes, which were renamed to `From/ToForm` to `http-api-data` +* BACKWARDS INCOMPATIBLE: replace use of `ToFromByteString` with `To/FromHttpApiData` for `GetHeaders/BuildHeadersTo` +* BACKWARDS INCOMPATIBLE: Moved `From/ToFormUrlEncoded` classes, which were renamed to `From/ToForm` to `http-api-data` 0.8.1 ---- From bc1a3e7faaf040ae26463c58177927414f5bdbc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Sat, 17 Sep 2016 11:47:57 -0400 Subject: [PATCH 43/43] bump version --- doc/tutorial/tutorial.cabal | 12 ++++++------ servant-client/servant-client.cabal | 8 ++++---- servant-docs/servant-docs.cabal | 4 ++-- servant-foreign/servant-foreign.cabal | 4 ++-- servant-js/servant-js.cabal | 10 +++++----- servant-server/servant-server.cabal | 4 ++-- servant/servant.cabal | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/doc/tutorial/tutorial.cabal b/doc/tutorial/tutorial.cabal index 797ff5e2..18b54c40 100644 --- a/doc/tutorial/tutorial.cabal +++ b/doc/tutorial/tutorial.cabal @@ -1,5 +1,5 @@ name: tutorial -version: 0.8.1 +version: 0.9 synopsis: The servant tutorial homepage: http://haskell-servant.readthedocs.org/ license: BSD3 @@ -25,11 +25,11 @@ library , directory , blaze-markup , containers - , servant == 0.8.* - , servant-server == 0.8.* - , servant-client == 0.8.* - , servant-docs == 0.8.* - , servant-js == 0.8.* + , servant == 0.9.* + , servant-server == 0.9.* + , servant-client == 0.9.* + , servant-docs == 0.9.* + , servant-js == 0.9.* , warp , http-media , lucid diff --git a/servant-client/servant-client.cabal b/servant-client/servant-client.cabal index 327f3d2d..1e4e1e8b 100644 --- a/servant-client/servant-client.cabal +++ b/servant-client/servant-client.cabal @@ -1,5 +1,5 @@ name: servant-client -version: 0.8.1 +version: 0.9 synopsis: automatical derivation of querying functions for servant webservices description: This library lets you derive automatically Haskell functions that @@ -48,7 +48,7 @@ library , http-types >= 0.8.6 && < 0.10 , network-uri >= 2.6 && < 2.7 , safe >= 0.3.9 && < 0.4 - , servant == 0.8.* + , servant == 0.9.* , string-conversions >= 0.3 && < 0.5 , text >= 1.2 && < 1.3 , transformers >= 0.3 && < 0.6 @@ -85,9 +85,9 @@ test-suite spec , HUnit , network >= 2.6 , QuickCheck >= 2.7 - , servant == 0.8.* + , servant == 0.9.* , servant-client - , servant-server == 0.8.* + , servant-server == 0.9.* , text , wai , warp diff --git a/servant-docs/servant-docs.cabal b/servant-docs/servant-docs.cabal index 949abece..70bbeeff 100644 --- a/servant-docs/servant-docs.cabal +++ b/servant-docs/servant-docs.cabal @@ -1,5 +1,5 @@ name: servant-docs -version: 0.8.1 +version: 0.9 synopsis: generate API docs for your servant webservice description: Library for generating API docs from a servant API definition. @@ -41,7 +41,7 @@ library , http-media >= 0.6 , http-types >= 0.7 , lens - , servant == 0.8.* + , servant == 0.9.* , string-conversions , text , unordered-containers diff --git a/servant-foreign/servant-foreign.cabal b/servant-foreign/servant-foreign.cabal index 26be4dbd..c26b879b 100644 --- a/servant-foreign/servant-foreign.cabal +++ b/servant-foreign/servant-foreign.cabal @@ -1,5 +1,5 @@ name: servant-foreign -version: 0.8.1 +version: 0.9 synopsis: Helpers for generating clients for servant APIs in any programming language description: Helper types and functions for generating client functions for servant APIs in any programming language @@ -32,7 +32,7 @@ library , Servant.Foreign.Inflections build-depends: base == 4.* , lens == 4.* - , servant == 0.8.* + , servant == 0.9.* , text >= 1.2 && < 1.3 , http-types hs-source-dirs: src diff --git a/servant-js/servant-js.cabal b/servant-js/servant-js.cabal index 04ea1ff9..a79f5fda 100644 --- a/servant-js/servant-js.cabal +++ b/servant-js/servant-js.cabal @@ -1,5 +1,5 @@ name: servant-js -version: 0.8.1 +version: 0.9 synopsis: Automatically derive javascript functions to query servant webservices. description: Automatically derive javascript functions to query servant webservices. @@ -45,8 +45,8 @@ library , base-compat >= 0.9 , charset >= 0.3 , lens >= 4 - , servant-foreign == 0.8.* - , servant == 0.8.* + , servant-foreign == 0.9.* + , servant == 0.9.* , text >= 1.2 && < 1.3 hs-source-dirs: src @@ -68,8 +68,8 @@ executable counter , aeson >= 0.7 && < 1.1 , filepath >= 1 , lens >= 4 - , servant == 0.8.* - , servant-server == 0.8.* + , servant == 0.9.* + , servant-server == 0.9.* , servant-js , stm , transformers diff --git a/servant-server/servant-server.cabal b/servant-server/servant-server.cabal index f9b5f45f..cbd30cef 100644 --- a/servant-server/servant-server.cabal +++ b/servant-server/servant-server.cabal @@ -1,5 +1,5 @@ name: servant-server -version: 0.8.1 +version: 0.9 synopsis: A family of combinators for defining webservices APIs and serving them description: A family of combinators for defining webservices APIs and serving them @@ -58,7 +58,7 @@ library , mtl >= 2 && < 2.3 , network >= 2.6 && < 2.7 , safe >= 0.3 && < 0.4 - , servant == 0.8.* + , servant == 0.9.* , split >= 0.2 && < 0.3 , string-conversions >= 0.3 && < 0.5 , system-filepath >= 0.4 && < 0.5 diff --git a/servant/servant.cabal b/servant/servant.cabal index 6d3030bf..76421783 100644 --- a/servant/servant.cabal +++ b/servant/servant.cabal @@ -1,5 +1,5 @@ name: servant -version: 0.8.1 +version: 0.9 synopsis: A family of combinators for defining webservices APIs description: A family of combinators for defining webservices APIs and serving them