on master help sign in
Merge remote-tracking branch 'origin/master' into client-ghcjs_update-stack-file
commit6c5afe8fb3592b1364e2688b61c6007392047fa9 authorSönke Hahn <soenkehahn@gmail.com> dateSat 9 Jul 2016, 11:11 (10 years ago) parentcd22bf7af7391c
90 files changed+1543−1305
.travis.yml+7−4 README.md+2−1 doc/examples.md+37 doc/index.rst+1 doc/tutorial/ApiType.lhs+1−1 doc/tutorial/Authentication.lhs+15−15 doc/tutorial/Server.lhs+54−58 doc/tutorial/index.rst+2−8 doc/tutorial/tutorial.cabal+9−13 scripts/test-stack.sh+11 scripts/upload.hs+1−1 servant-blaze/LICENSE−30 servant-blaze/Setup.hs−2 servant-blaze/include/overlapping-compat.h−8 servant-blaze/servant-blaze.cabal−33 servant-blaze/src/Servant/HTML/Blaze.hs−35 servant-blaze/tinc.yaml−3 servant-cassava/LICENSE−30 servant-cassava/Setup.hs−2 servant-cassava/include/overlapping-compat.h−8 servant-cassava/servant-cassava.cabal−30 servant-cassava/src/Servant/CSV/Cassava.hs−115 servant-cassava/tinc.yaml−3 servant-client/CHANGELOG.md+6 servant-client/README.md+3−4 servant-client/servant-client.cabal+15−10 servant-client/src/Servant/Client.hs+43−42 servant-client/src/Servant/Client/PerformRequest/Base.hs+13 servant-client/src/Servant/Common/Req.hs+9−1 servant-client/test/Servant/ClientSpec.hs+8−5 servant-docs/CHANGELOG.md+10 servant-docs/servant-docs.cabal+5−4 servant-docs/src/Servant/Docs/Internal.hs+53−53 servant-docs/src/Servant/Docs/Internal/Pretty.hs+2−2 servant-foreign/CHANGELOG.md+5 servant-foreign/servant-foreign.cabal+5−2 servant-foreign/src/Servant/Foreign/Internal.hs+52−51 servant-js/servant-js.cabal+6−6 servant-js/src/Servant/JS.hs+2−2 servant-js/src/Servant/JS/Internal.hs+1−2 servant-js/test/Servant/JSSpec/CustomHeaders.hs+12−12 servant-lucid/LICENSE−30 servant-lucid/Setup.hs−2 servant-lucid/include/overlapping-compat.h−8 servant-lucid/servant-lucid.cabal−33 servant-lucid/src/Servant/HTML/Lucid.hs−36 servant-lucid/tinc.yaml−3 servant-mock/example/main.hs+3 servant-mock/servant-mock.cabal+11−6 servant-mock/src/Servant/Mock.hs+5−5 servant-server/CHANGELOG.md+30 servant-server/README.md+1−2 servant-server/example/greet.hs+1−1 servant-server/servant-server.cabal+15−14 servant-server/src/Servant.hs+2 servant-server/src/Servant/Server.hs+74−8 servant-server/src/Servant/Server/Experimental/Auth.hs+14−12 servant-server/src/Servant/Server/Internal.hs+114−114 servant-server/src/Servant/Server/Internal/BasicAuth.hs+12−11 servant-server/src/Servant/Server/Internal/Context.hs+2−2 servant-server/src/Servant/Server/Internal/Router.hs+168−61 servant-server/src/Servant/Server/Internal/RoutingApplication.hs+136−127 servant-server/src/Servant/Server/Internal/ServantErr.hs+63−60 servant-server/test/Servant/Server/Internal/EnterSpec.hs → servant-server/test/Servant/ArbitraryMonadServerSpec.hs+3−4 servant-server/test/Servant/Server/ErrorSpec.hs+24−7 servant-server/test/Servant/Server/Internal/ContextSpec.hs+5−4 servant-server/test/Servant/Server/RouterSpec.hs+294 servant-server/test/Servant/Server/StreamingSpec.hs+2−3 servant-server/test/Servant/Server/UsingContextSpec.hs+1−2 servant-server/test/Servant/Server/UsingContextSpec/TestCombinators.hs+2−3 servant-server/test/Servant/ServerSpec.hs+65−58 servant/CHANGELOG.md+7−1 servant/servant.cabal+15−8 servant/src/Servant/API/Alternative.hs−2 servant/src/Servant/API/BasicAuth.hs+4−3 servant/src/Servant/API/ContentTypes.hs+2−2 servant/src/Servant/API/Experimental/Auth.hs+2−3 servant/src/Servant/API/Header.hs+3−3 servant/src/Servant/API/ResponseHeaders.hs+5−11 servant/src/Servant/API/Vault.hs+2−2 servant/src/Servant/API/Verbs.hs+12−1 servant-server/src/Servant/Server/Internal/Enter.hs → servant/src/Servant/Utils/Enter.hs+1−4 servant/src/Servant/Utils/Links.hs+12−8 servant/test/Servant/API/ContentTypesSpec.hs+5−5 servant/test/Servant/Utils/LinksSpec.hs+5−5 sources.txt+1−4 stack-ghc-7.8.4.yaml+16−16 stack-ghc-8.0.1.yaml+11 stack.yaml+2−14 travis.sh+1−1
.travis.yml+7−4
@@ -3,8 +3,9 @@ sudo: false
33language: c
44
55env:
6− - GHCVER=7.8.4
7− - GHCVER=7.10.2
6+ - GHCVER=7.8.4 CABALVER=1.22
7+ - GHCVER=7.10.3 CABALVER=1.22
8+ - GHCVER=8.0.1 CABALVER=1.24
89
910addons:
1011 apt:
@@ -12,13 +13,15 @@ addons:
1213 - hvr-ghc
1314 packages:
1415 - ghc-7.8.4
15− - ghc-7.10.2
16+ - ghc-7.10.3
17+ - ghc-8.0.1
1618 - cabal-install-1.22
19+ - cabal-install-1.24
1720 - libgmp-dev
1821
1922install:
2023 - (mkdir -p $HOME/.local/bin && cd $HOME/.local/bin && wget https://zalora-public.s3.amazonaws.com/tinc && chmod +x tinc)
21− - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/1.22/bin:$PATH
24+ - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
2225 - ghc --version
2326 - cabal --version
2427 - travis_retry cabal update
README.md+2−1
@@ -4,11 +4,12 @@
44
55## Getting Started
66
7−We have a [tutorial](http://haskell-servant.github.io/tutorial) that
7+We have a [tutorial](http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html) that
88introduces the core features of servant. After this article, you should be able
99to write your first servant webservices, learning the rest from the haddocks'
1010examples.
1111
12+The central documentation can be found [here](http://haskell-servant.readthedocs.org/).
1213Other blog posts, videos and slides can be found on the
1314[website](http://haskell-servant.github.io/).
1415
doc/examples.mdnew file+37
@@ -0,0 +1,37 @@
1+# Example Projects
2+
3+- **[example-servant-minimal](https://github.com/haskell-servant/example-servant-minimal)**:
4+
5+ A minimal example for a web server written using **servant-server**,
6+ including a test-suite using [**hspec**](http://hspec.github.io/) and
7+ **servant-client**.
8+
9+
10+- **[stack-templates](https://github.com/commercialhaskell/stack-templates)**
11+
12+ Repository for templates for haskell projects, including some templates using
13+ **servant**. These templates can be used with `stack new`.
14+
15+- **[custom-monad](https://github.com/themoritz/diener)**:
16+
17+ A custom monad that can replace `IO` in servant applications. It adds among
18+ other things logging functionality and a reader monad (for database connections).
19+ A full usage example of servant/diener is also provided.
20+
21+
22+- **[example-servant-elm](https://github.com/haskell-servant/example-servant-elm)**:
23+
24+ An example for a project consisting of
25+
26+ - a backend web server written using **servant-server**,
27+ - a frontend written in [elm](http://elm-lang.org/) using
28+ [servant-elm](https://github.com/mattjbray/servant-elm) to generate client
29+ functions in elm for the API,
30+ - test-suites for both the backend and the frontend.
31+
32+
33+- **[example-servant-persistent](https://github.com/haskell-servant/example-servant-persistent)**:
34+
35+ An example for a web server written with **servant-server** and
36+ [persistent](https://www.stackage.org/package/persistent) for writing data
37+ into a database.
doc/index.rst+1
@@ -19,4 +19,5 @@ All in a type-safe manner.
1919
2020 introduction.rst
2121 tutorial/index.rst
22+ examples.md
2223 links.rst
doc/tutorial/ApiType.lhs+1−1
@@ -297,7 +297,7 @@ Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
297297
298298When protecting endpoints with basic authentication, we need to specify two items:
299299
300−1. The **realm** of authentication as per the Basic Authentictaion spec.
300+1. The **realm** of authentication as per the Basic Authentication spec.
3013012. The datatype returned by the server after authentication is verified. This
302302 is usually a `User` or `Customer` type datatype.
303303
doc/tutorial/Authentication.lhs+15−15
@@ -44,7 +44,6 @@ You can use this combinator to protect an API as follows:
4444
4545module Authentication where
4646
47−import Control.Monad.Trans.Except (ExceptT, throwE)
4847import Data.Aeson (ToJSON)
4948import Data.ByteString (ByteString)
5049import Data.Map (Map, fromList)
@@ -59,13 +58,14 @@ import Servant.API ((:<|>) ((:<|>)), (:>), BasicAuth,
5958 Get, JSON)
6059import Servant.API.BasicAuth (BasicAuthData (BasicAuthData))
6160import Servant.API.Experimental.Auth (AuthProtect)
61+import Servant (throwError)
6262import Servant.Server (BasicAuthCheck (BasicAuthCheck),
6363 BasicAuthResult( Authorized
6464 , Unauthorized
6565 ),
6666 Context ((:.), EmptyContext),
6767 err401, err403, errBody, Server,
68− ServantErr, serveWithContext)
68+ serveWithContext, Handler)
6969import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData,
7070 mkAuthHandler)
7171import Servant.Server.Experimental.Auth()
@@ -117,22 +117,22 @@ or dictated the structure of a response (e.g. a `Capture` param is pulled from
117117the request path). Now consider an API resource protected by basic
118118authentication. Once the required `WWW-Authenticate` header is checked, we need
119119to verify the username and password. But how? One solution would be to force an
120−API author to provide a function of type `BasicAuthData -> ExceptT ServantErr IO User`
120+API author to provide a function of type `BasicAuthData -> Handler User`
121121and servant should use this function to authenticate a request. Unfortunately
122122this didn't work prior to `0.5` because all of servant's machinery was
123123engineered around the idea that each combinator can extract information from
124124only the request. We cannot extract the function
125−`BasicAuthData -> ExceptT ServantErr IO User` from a request! Are we doomed?
125+`BasicAuthData -> Handler User` from a request! Are we doomed?
126126
127127Servant `0.5` introduced `Context` to handle this. The type machinery is beyond
128128the scope of this tutorial, but the idea is simple: provide some data to the
129129`serve` function, and that data is propagated to the functions that handle each
130130combinator. Using `Context`, we can supply a function of type
131−`BasicAuthData -> ExceptT ServantErr IO User` to the `BasicAuth` combinator
131+`BasicAuthData -> Handler User` to the `BasicAuth` combinator
132132handler. This will allow the handler to check authentication and return a `User`
133133to downstream handlers if successful.
134134
135−In practice we wrap `BasicAuthData -> ExceptT ServantErr IO` into a slightly
135+In practice we wrap `BasicAuthData -> Handler` into a slightly
136136different function to better capture the semantics of basic authentication:
137137
138138``` haskell ignore
@@ -173,7 +173,7 @@ And now we create the `Context` used by servant to find `BasicAuthCheck`:
173173```haskell
174174-- | We need to supply our handlers with the right Context. In this case,
175175-- Basic Authentication requires a Context Entry with the 'BasicAuthCheck' value
176−-- tagged with "foo-tag" This context is then supplied to 'server' and threaded
176+-- tagged with "foo-tag" This context is then supplied to 'server' and threaded
177177-- to the BasicAuth HasServer handlers.
178178basicAuthServerContext :: Context (BasicAuthCheck User ': '[])
179179basicAuthServerContext = authCheck :. EmptyContext
@@ -246,7 +246,7 @@ your feedback!
246246### What is Generalized Authentication?
247247
248248**TL;DR**: you throw a tagged `AuthProtect` combinator in front of the endpoints
249−you want protected and then supply a function `Request -> ExceptT IO ServantErr user`
249+you want protected and then supply a function `Request -> Handler user`
250250which we run anytime a request matches a protected endpoint. It precisely solves
251251the "I just need to protect these endpoints with a function that does some
252252complicated business logic" and nothing more. Behind the scenes we use a type
@@ -272,24 +272,24 @@ database = fromList [ ("key1", Account "Anne Briggs")
272272
273273-- | A method that, when given a password, will return a Account.
274274-- This is our bespoke (and bad) authentication logic.
275−lookupAccount :: ByteString -> ExceptT ServantErr IO Account
275+lookupAccount :: ByteString -> Handler Account
276276lookupAccount key = case Map.lookup key database of
277− Nothing -> throwE (err403 { errBody = "Invalid Cookie" })
277+ Nothing -> throwError (err403 { errBody = "Invalid Cookie" })
278278 Just usr -> return usr
279279```
280280
281281For generalized authentication, servant exposes the `AuthHandler` type,
282−which is used to wrap the `Request -> ExceptT IO ServantErr user` logic. Let's
282+which is used to wrap the `Request -> Handler user` logic. Let's
283283create a value of type `AuthHandler Request Account` using the above `lookupAccount`
284284method:
285285
286286```haskell
287−-- | The auth handler wraps a function from Request -> ExceptT ServantErr IO Account
287+-- | The auth handler wraps a function from Request -> Handler Account
288288-- we look for a Cookie and pass the value of the cookie to `lookupAccount`.
289289authHandler :: AuthHandler Request Account
290290authHandler =
291291 let handler req = case lookup "servant-auth-cookie" (requestHeaders req) of
292− Nothing -> throwE (err401 { errBody = "Missing auth header" })
292+ Nothing -> throwError (err401 { errBody = "Missing auth header" })
293293 Just authCookieKey -> lookupAccount authCookieKey
294294 in mkAuthHandler handler
295295```
@@ -329,7 +329,7 @@ We now construct the `Context` for our server, allowing us to instantiate a
329329value of type `Server AuthGenAPI`, in addition to the server value:
330330
331331```haskell
332−-- | The context that will be made available to request handlers. We supply the
332+-- | The context that will be made available to request handlers. We supply the
333333-- "cookie-auth"-tagged request handler defined above, so that the 'HasServer' instance
334334-- of 'AuthProtect' can extract the handler and run it on the request.
335335genAuthServerContext :: Context (AuthHandler Request Account ': '[])
@@ -379,7 +379,7 @@ forward:
3793792. choose a application-specific data type used by your server when
380380authentication is successful (in our case this was `User`).
3813813. Create a value of `AuthHandler Request User` which encapsulates the
382−authentication logic (`Request -> ExceptT IO ServantErr User`). This function
382+authentication logic (`Request -> Handler User`). This function
383383will be executed everytime a request matches a protected route.
3843844. Provide an instance of the `AuthServerData` type family, specifying your
385385application-specific data type returned when authentication is successful (in
doc/tutorial/Server.lhs+54−58
@@ -111,11 +111,11 @@ corresponding API type.
111111The first thing to know about the `Server` type family is that behind the
112112scenes it will drive the routing, letting you focus only on the business
113113logic. The second thing to know is that for each endpoint, your handlers will
114−by default run in the `ExceptT ServantErr IO` monad. This is overridable very
114+by default run in the `Handler` monad. This is overridable very
115115easily, as explained near the end of this guide. Third thing, the type of the
116116value returned in that monad must be the same as the second argument of the
117117HTTP method combinator used for the corresponding endpoint. In our case, it
118−means we must provide a handler of type `ExceptT ServantErr IO [User]`. Well,
118+means we must provide a handler of type `Handler [User]`. Well,
119119we have a monad, let's just `return` our list:
120120
121121``` haskell
@@ -269,15 +269,15 @@ server3 = position
269269 :<|> hello
270270 :<|> marketing
271271
272− where position :: Int -> Int -> ExceptT ServantErr IO Position
272+ where position :: Int -> Int -> Handler Position
273273 position x y = return (Position x y)
274274
275− hello :: Maybe String -> ExceptT ServantErr IO HelloMessage
275+ hello :: Maybe String -> Handler HelloMessage
276276 hello mname = return . HelloMessage $ case mname of
277277 Nothing -> "Hello, anonymous coward"
278278 Just n -> "Hello, " ++ n
279279
280− marketing :: ClientInfo -> ExceptT ServantErr IO Email
280+ marketing :: ClientInfo -> Handler Email
281281 marketing clientinfo = return (emailForClient clientinfo)
282282```
283283
@@ -307,7 +307,7 @@ $ curl -X POST -d '{"clientName":"Alp Mestanogullari", "clientEmail" : "alp@foo.
307307
308308For reference, here's a list of some combinators from **servant**:
309309
310− > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `ExceptT ServantErr IO <something>`.
310+ > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `Handler <something>`.
311311 > - `Capture "something" a` becomes an argument of type `a`.
312312 > - `QueryParam "something" a`, `Header "something" a` all become arguments of type `Maybe a`, because there might be no value at all specified by the client for these.
313313 > - `QueryFlag "something"` gets turned into an argument of type `Bool`.
@@ -601,11 +601,10 @@ $ curl -H 'Accept: text/html' http://localhost:8081/persons
601601# or just point your browser to http://localhost:8081/persons
602602```
603603
604−## The `ExceptT ServantErr IO` monad
604+## The `Handler` monad
605605
606−At the heart of the handlers is the monad they run in, namely `ExceptT
607−ServantErr IO`
608−([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
606+At the heart of the handlers is the monad they run in, namely `ExceptT ServantErr IO`
607+([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)), which is aliased as `Handler`.
609608One might wonder: why this monad? The answer is that it is the
610609simplest monad with the following properties:
611610
@@ -621,7 +620,7 @@ Let's recall some definitions.
621620newtype ExceptT e m a = ExceptT (m (Either e a))
622621```
623622
624−In short, this means that a handler of type `ExceptT ServantErr IO a` is simply
623+In short, this means that a handler of type `Handler a` is simply
625624equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
626625action that either returns an error or a result.
627626
@@ -688,7 +687,7 @@ module. If you want to use these values but add a body or some headers, just
688687use record update syntax:
689688
690689``` haskell
691−failingHandler :: ExceptT ServantErr IO ()
690+failingHandler :: Handler ()
692691failingHandler = throwError myerr
693692
694693 where myerr :: ServantErr
@@ -810,7 +809,7 @@ type UserAPI3 = -- view the user with given userid, in JSON
810809 Capture "userid" Int :> Get '[JSON] User
811810
812811 :<|> -- delete the user with given userid. empty response
813− Capture "userid" Int :> Delete '[] ()
812+ Capture "userid" Int :> DeleteNoContent '[JSON] NoContent
814813```
815814
816815We can instead factor out the `userid`:
@@ -818,7 +817,7 @@ We can instead factor out the `userid`:
818817``` haskell
819818type UserAPI4 = Capture "userid" Int :>
820819 ( Get '[JSON] User
821− :<|> Delete '[] ()
820+ :<|> DeleteNoContent '[JSON] NoContent
822821 )
823822```
824823
@@ -826,11 +825,11 @@ However, you have to be aware that this has an effect on the type of the
826825corresponding `Server`:
827826
828827``` haskell ignore
829−Server UserAPI3 = (Int -> ExceptT ServantErr IO User)
830− :<|> (Int -> ExceptT ServantErr IO ())
828+Server UserAPI3 = (Int -> Handler User)
829+ :<|> (Int -> Handler NoContent)
831830
832−Server UserAPI4 = Int -> ( ExceptT ServantErr IO User
833− :<|> ExceptT ServantErr IO ()
831+Server UserAPI4 = Int -> ( Handler User
832+ :<|> Handler NoContent
834833 )
835834```
836835
@@ -842,10 +841,10 @@ computations in `ExceptT`, with no arguments. In other words:
842841server8 :: Server UserAPI3
843842server8 = getUser :<|> deleteUser
844843
845− where getUser :: Int -> ExceptT ServantErr IO User
844+ where getUser :: Int -> Handler User
846845 getUser _userid = error "..."
847846
848− deleteUser :: Int -> ExceptT ServantErr IO ()
847+ deleteUser :: Int -> Handler NoContent
849848 deleteUser _userid = error "..."
850849
851850-- notice how getUser and deleteUser
@@ -854,10 +853,10 @@ server8 = getUser :<|> deleteUser
854853server9 :: Server UserAPI4
855854server9 userid = getUser userid :<|> deleteUser userid
856855
857− where getUser :: Int -> ExceptT ServantErr IO User
856+ where getUser :: Int -> Handler User
858857 getUser = error "..."
859858
860− deleteUser :: Int -> ExceptT ServantErr IO ()
859+ deleteUser :: Int -> Handler NoContent
861860 deleteUser = error "..."
862861```
863862
@@ -876,13 +875,13 @@ type API1 = "users" :>
876875-- we factor out the Request Body
877876type API2 = ReqBody '[JSON] User :>
878877 ( Get '[JSON] User -- just display the same user back, don't register it
879− :<|> Post '[JSON] () -- register the user. empty response
878+ :<|> PostNoContent '[JSON] NoContent -- register the user. empty response
880879 )
881880
882881-- we factor out a Header
883882type API3 = Header "Authorization" Token :>
884883 ( Get '[JSON] SecretData -- get some secret data, if authorized
885− :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
884+ :<|> ReqBody '[JSON] SecretData :> PostNoContent '[JSON] NoContent -- add some secret data, if authorized
886885 )
887886
888887newtype Token = Token ByteString
@@ -895,44 +894,44 @@ API type only at the end.
895894``` haskell
896895type UsersAPI =
897896 Get '[JSON] [User] -- list users
898− :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
897+ :<|> ReqBody '[JSON] User :> PostNoContent '[JSON] NoContent -- add a user
899898 :<|> Capture "userid" Int :>
900899 ( Get '[JSON] User -- view a user
901− :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
902− :<|> Delete '[] () -- delete a user
900+ :<|> ReqBody '[JSON] User :> PutNoContent '[JSON] NoContent -- update a user
901+ :<|> DeleteNoContent '[JSON] NoContent -- delete a user
903902 )
904903
905904usersServer :: Server UsersAPI
906905usersServer = getUsers :<|> newUser :<|> userOperations
907906
908− where getUsers :: ExceptT ServantErr IO [User]
907+ where getUsers :: Handler [User]
909908 getUsers = error "..."
910909
911− newUser :: User -> ExceptT ServantErr IO ()
910+ newUser :: User -> Handler NoContent
912911 newUser = error "..."
913912
914913 userOperations userid =
915914 viewUser userid :<|> updateUser userid :<|> deleteUser userid
916915
917916 where
918− viewUser :: Int -> ExceptT ServantErr IO User
917+ viewUser :: Int -> Handler User
919918 viewUser = error "..."
920919
921− updateUser :: Int -> User -> ExceptT ServantErr IO ()
920+ updateUser :: Int -> User -> Handler NoContent
922921 updateUser = error "..."
923922
924− deleteUser :: Int -> ExceptT ServantErr IO ()
923+ deleteUser :: Int -> Handler NoContent
925924 deleteUser = error "..."
926925```
927926
928927``` haskell
929928type ProductsAPI =
930929 Get '[JSON] [Product] -- list products
931− :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
930+ :<|> ReqBody '[JSON] Product :> PostNoContent '[JSON] NoContent -- add a product
932931 :<|> Capture "productid" Int :>
933932 ( Get '[JSON] Product -- view a product
934− :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
935− :<|> Delete '[] () -- delete a product
933+ :<|> ReqBody '[JSON] Product :> PutNoContent '[JSON] NoContent -- update a product
934+ :<|> DeleteNoContent '[JSON] NoContent -- delete a product
936935 )
937936
938937data Product = Product { productId :: Int }
@@ -940,23 +939,23 @@ data Product = Product { productId :: Int }
940939productsServer :: Server ProductsAPI
941940productsServer = getProducts :<|> newProduct :<|> productOperations
942941
943− where getProducts :: ExceptT ServantErr IO [Product]
942+ where getProducts :: Handler [Product]
944943 getProducts = error "..."
945944
946− newProduct :: Product -> ExceptT ServantErr IO ()
945+ newProduct :: Product -> Handler NoContent
947946 newProduct = error "..."
948947
949948 productOperations productid =
950949 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
951950
952951 where
953− viewProduct :: Int -> ExceptT ServantErr IO Product
952+ viewProduct :: Int -> Handler Product
954953 viewProduct = error "..."
955954
956− updateProduct :: Int -> Product -> ExceptT ServantErr IO ()
955+ updateProduct :: Int -> Product -> Handler NoContent
957956 updateProduct = error "..."
958957
959− deleteProduct :: Int -> ExceptT ServantErr IO ()
958+ deleteProduct :: Int -> Handler NoContent
960959 deleteProduct = error "..."
961960```
962961
@@ -976,20 +975,20 @@ abstract that away:
976975-- indexed by values of type 'i'
977976type APIFor a i =
978977 Get '[JSON] [a] -- list 'a's
979− :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
978+ :<|> ReqBody '[JSON] a :> PostNoContent '[JSON] NoContent -- add an 'a'
980979 :<|> Capture "id" i :>
981980 ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
982− :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
983− :<|> Delete '[] () -- delete an 'a'
981+ :<|> ReqBody '[JSON] a :> PutNoContent '[JSON] NoContent -- update an 'a'
982+ :<|> DeleteNoContent '[JSON] NoContent -- delete an 'a'
984983 )
985984
986985-- Build the appropriate 'Server'
987986-- given the handlers of the right type.
988−serverFor :: ExceptT ServantErr IO [a] -- handler for listing of 'a's
989− -> (a -> ExceptT ServantErr IO ()) -- handler for adding an 'a'
990− -> (i -> ExceptT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
991− -> (i -> a -> ExceptT ServantErr IO ()) -- updating an 'a' with given id
992− -> (i -> ExceptT ServantErr IO ()) -- deleting an 'a' given its id
987+serverFor :: Handler [a] -- handler for listing of 'a's
988+ -> (a -> Handler NoContent) -- handler for adding an 'a'
989+ -> (i -> Handler a) -- handler for viewing an 'a' given its identifier of type 'i'
990+ -> (i -> a -> Handler NoContent) -- updating an 'a' with given id
991+ -> (i -> Handler NoContent) -- deleting an 'a' given its id
993992 -> Server (APIFor a i)
994993serverFor = error "..."
995994-- implementation left as an exercise. contact us on IRC
@@ -998,12 +997,11 @@ serverFor = error "..."
998997
999998## Using another monad for your handlers
1000999
1001−Remember how `Server` turns combinators for HTTP methods into `ExceptT
1002−ServantErr IO`? Well, actually, there's more to that. `Server` is actually a
1000+Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
10031001simple type synonym.
10041002
10051003``` haskell ignore
1006−type Server api = ServerT api (ExceptT ServantErr IO)
1004+type Server api = ServerT api Handler
10071005```
10081006
10091007`ServerT` is the actual type family that computes the required types for the
@@ -1036,12 +1034,11 @@ listToMaybeNat = Nat listToMaybe -- from Data.Maybe
10361034
10371035(`Nat` comes from "natural transformation", in case you're wondering.)
10381036
1039−So if you want to write handlers using another monad/type than `ExceptT
1040−ServantErr IO`, say the `Reader String` monad, the first thing you have to
1037+So if you want to write handlers using another monad/type than `Handler`, say the `Reader String` monad, the first thing you have to
10411038prepare is a function:
10421039
10431040``` haskell ignore
1044−readerToHandler :: Reader String :~> ExceptT ServantErr IO
1041+readerToHandler :: Reader String :~> Handler
10451042```
10461043
10471044Let's start with `readerToHandler'`. We obviously have to run the `Reader`
@@ -1050,10 +1047,10 @@ from that and can then just `return` it into `ExceptT`. We can then just wrap
10501047that function with the `Nat` constructor to make it have the fancier type.
10511048
10521049``` haskell
1053−readerToHandler' :: forall a. Reader String a -> ExceptT ServantErr IO a
1050+readerToHandler' :: forall a. Reader String a -> Handler a
10541051readerToHandler' r = return (runReader r "hi")
10551052
1056−readerToHandler :: Reader String :~> ExceptT ServantErr IO
1053+readerToHandler :: Reader String :~> Handler
10571054readerToHandler = Nat readerToHandler'
10581055```
10591056
@@ -1077,8 +1074,7 @@ readerServerT = a :<|> b
10771074```
10781075
10791076We unfortunately can't use `readerServerT` as an argument of `serve`, because
1080−`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT
1081−ServantErr IO`. But there's a simple solution to this.
1077+`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `Handler`. But there's a simple solution to this.
10821078
10831079### Enter `enter`
10841080
doc/tutorial/index.rst+2−8
@@ -3,14 +3,8 @@ Tutorial
33
44This is an introductory tutorial to **servant**.
55
6−.. note::
7− This tutorial is for the latest version of servant. The tutorial for
8− servant-0.4 can be viewed
9− `here <https://haskell-servant.github.io/tutorial/>`_.
10−
11−(Any comments, issues or feedback about the tutorial can be handled
12−through
13−`servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.)
6+(Any comments, issues or feedback about the tutorial can be submitted
7+to `servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.)
148
159
1610.. toctree::
doc/tutorial/tutorial.cabal+9−13
@@ -1,7 +1,7 @@
11name: tutorial
2−version: 0.6
2+version: 0.7.1
33synopsis: The servant tutorial
4−homepage: http://haskell-servant.github.io/
4+homepage: http://haskell-servant.readthedocs.org/
55license: BSD3
66license-file: LICENSE
77author: Servant Contributors
@@ -25,11 +25,11 @@ library
2525 , directory
2626 , blaze-markup
2727 , containers
28− , servant == 0.6.*
29− , servant-server == 0.6.*
30− , servant-client == 0.6.*
31− , servant-docs == 0.6.*
32− , servant-js == 0.6.*
28+ , servant == 0.7.*
29+ , servant-server == 0.7.*
30+ , servant-client == 0.7.*
31+ , servant-docs == 0.7.*
32+ , servant-js == 0.7.*
3333 , warp
3434 , http-media
3535 , lucid
@@ -46,15 +46,11 @@ library
4646 , markdown-unlit >= 0.4
4747 , http-client
4848 default-language: Haskell2010
49− ghc-options: -Wall -Werror -pgmL markdown-unlit
50− -- to silence aeson-0.10 warnings:
51− ghc-options: -fno-warn-missing-methods
52− ghc-options: -fno-warn-name-shadowing
49+ ghc-options: -Wall -pgmL markdown-unlit
5350
5451test-suite spec
5552 type: exitcode-stdio-1.0
56− ghc-options:
57− -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures
53+ ghc-options: -Wall
5854 default-language: Haskell2010
5955 hs-source-dirs: test
6056 main-is: Spec.hs
scripts/test-stack.shnew file+11
@@ -0,0 +1,11 @@
1+#!/usr/bin/env bash
2+
3+set -o nounset
4+set -o errexit
5+
6+for stack_file in stack*.yaml ; do
7+ echo testing $stack_file...
8+ export STACK_YAML=$stack_file
9+ stack setup
10+ stack test --fast --ghc-options="-Werror"
11+done
scripts/upload.hs+1−1
@@ -11,4 +11,4 @@ main :: IO ()
1111main = do
1212 sources <- words <$> readFile "sources.txt"
1313 forM_ sources $ \ source -> do
14− callCommand ("stack upload " ++ source)
14+ callCommand ("stack upload --no-signature " ++ source)
servant-blaze/LICENSEdeleted−30
@@ -1,30 +0,0 @@
1−Copyright (c) 2015-2016, Servant Contributors
2−
3−All rights reserved.
4−
5−Redistribution and use in source and binary forms, with or without
6−modification, are permitted provided that the following conditions are met:
7−
8− * Redistributions of source code must retain the above copyright
9− notice, this list of conditions and the following disclaimer.
10−
11− * Redistributions in binary form must reproduce the above
12− copyright notice, this list of conditions and the following
13− disclaimer in the documentation and/or other materials provided
14− with the distribution.
15−
16− * Neither the name of Julian K. Arni nor the names of other
17− contributors may be used to endorse or promote products derived
18− from this software without specific prior written permission.
19−
20−THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21−"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22−LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23−A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24−OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25−SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26−LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27−DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28−THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29−(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30−OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-blaze/Setup.hsdeleted−2
@@ -1,2 +0,0 @@
1−import Distribution.Simple
2−main = defaultMain
servant-blaze/include/overlapping-compat.hdeleted−8
@@ -1,8 +0,0 @@
1−#if __GLASGOW_HASKELL__ >= 710
2−#define OVERLAPPABLE_ {-# OVERLAPPABLE #-}
3−#define OVERLAPPING_ {-# OVERLAPPING #-}
4−#else
5−{-# LANGUAGE OverlappingInstances #-}
6−#define OVERLAPPABLE_
7−#define OVERLAPPING_
8−#endif
servant-blaze/servant-blaze.cabaldeleted−33
@@ -1,33 +0,0 @@
1−-- Initial servant-blaze.cabal generated by cabal init. For further
2−-- documentation, see http://haskell.org/cabal/users-guide/
3−
4−name: servant-blaze
5−version: 0.6
6−synopsis: Blaze-html support for servant
7−-- description:
8−homepage: http://haskell-servant.github.io/
9−license: BSD3
10−license-file: LICENSE
11−author: Servant Contributors
12−maintainer: haskell-servant-maintainers@googlegroups.com
13−copyright: 2015-2016 Servant Contributors
14−category: Web
15−build-type: Simple
16−extra-source-files: include/*.h
17−cabal-version: >=1.10
18−bug-reports: http://github.com/haskell-servant/servant/issues
19−source-repository head
20− type: git
21− location: http://github.com/haskell-servant/servant.git
22−
23−library
24− exposed-modules: Servant.HTML.Blaze
25− -- other-modules:
26− -- other-extensions:
27− build-depends: base >=4.7 && <5
28− , servant == 0.6.*
29− , http-media
30− , blaze-html
31− hs-source-dirs: src
32− default-language: Haskell2010
33− include-dirs: include
servant-blaze/src/Servant/HTML/Blaze.hsdeleted−35
@@ -1,35 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE MultiParamTypeClasses #-}
5−{-# LANGUAGE OverloadedStrings #-}
6−
7−#include "overlapping-compat.h"
8−-- | An @HTML@ empty data type with `MimeRender` instances for @blaze-html@'s
9−-- `ToMarkup` class and `Html` datatype.
10−-- You should only need to import this module for it's instances and the
11−-- `HTML` datatype.:
12−--
13−-- >>> type Eg = Get '[HTML] a
14−--
15−-- Will then check that @a@ has a `ToMarkup` instance, or is `Html`.
16−module Servant.HTML.Blaze where
17−
18−import Data.Typeable (Typeable)
19−import qualified Network.HTTP.Media as M
20−import Servant.API (Accept (..), MimeRender (..))
21−import Text.Blaze.Html (Html, ToMarkup, toHtml)
22−import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
23−
24−data HTML deriving Typeable
25−
26−-- | @text/html;charset=utf-8@
27−instance Accept HTML where
28− contentType _ = "text" M.// "html" M./: ("charset", "utf-8")
29−
30−instance OVERLAPPABLE_ ToMarkup a => MimeRender HTML a where
31− mimeRender _ = renderHtml . toHtml
32−
33−instance OVERLAPPING_ MimeRender HTML Html where
34− mimeRender _ = renderHtml
35−
servant-blaze/tinc.yamldeleted−3
@@ -1,3 +0,0 @@
1−dependencies:
2− - name: servant
3− path: ../servant
servant-cassava/LICENSEdeleted−30
@@ -1,30 +0,0 @@
1−Copyright (c) 2015-2016, Servant Contributors
2−
3−All rights reserved.
4−
5−Redistribution and use in source and binary forms, with or without
6−modification, are permitted provided that the following conditions are met:
7−
8− * Redistributions of source code must retain the above copyright
9− notice, this list of conditions and the following disclaimer.
10−
11− * Redistributions in binary form must reproduce the above
12− copyright notice, this list of conditions and the following
13− disclaimer in the documentation and/or other materials provided
14− with the distribution.
15−
16− * Neither the name of Julian K. Arni nor the names of other
17− contributors may be used to endorse or promote products derived
18− from this software without specific prior written permission.
19−
20−THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21−"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22−LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23−A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24−OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25−SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26−LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27−DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28−THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29−(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30−OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-cassava/Setup.hsdeleted−2
@@ -1,2 +0,0 @@
1−import Distribution.Simple
2−main = defaultMain
servant-cassava/include/overlapping-compat.hdeleted−8
@@ -1,8 +0,0 @@
1−#if __GLASGOW_HASKELL__ >= 710
2−#define OVERLAPPABLE_ {-# OVERLAPPABLE #-}
3−#define OVERLAPPING_ {-# OVERLAPPING #-}
4−#else
5−{-# LANGUAGE OverlappingInstances #-}
6−#define OVERLAPPABLE_
7−#define OVERLAPPING_
8−#endif
servant-cassava/servant-cassava.cabaldeleted−30
@@ -1,30 +0,0 @@
1−-- Initial servant-cassava.cabal generated by cabal init. For further
2−-- documentation, see http://haskell.org/cabal/users-guide/
3−
4−name: servant-cassava
5−version: 0.6
6−synopsis: Servant CSV content-type for cassava
7−-- description:
8−homepage: http://haskell-servant.github.io/
9−license: BSD3
10−license-file: LICENSE
11−author: Servant Contributors
12−maintainer: haskell-servant-maintainers@googlegroups.com
13−copyright: 2015-2016 Servant Contributors
14−-- category:
15−build-type: Simple
16−extra-source-files: include/*.h
17−cabal-version: >=1.10
18−
19−library
20− exposed-modules: Servant.CSV.Cassava
21− -- other-modules:
22− -- other-extensions:
23− build-depends: base >=4.6 && <5
24− , cassava >0.4 && <0.5
25− , servant == 0.6.*
26− , http-media
27− , vector
28− hs-source-dirs: src
29− default-language: Haskell2010
30− include-dirs: include
servant-cassava/src/Servant/CSV/Cassava.hsdeleted−115
@@ -1,115 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE DeriveGeneric #-}
4−{-# LANGUAGE FlexibleInstances #-}
5−{-# LANGUAGE MultiParamTypeClasses #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
8−
9−-- | A @CSV@ empty datatype with `MimeRender` and `MimeUnrender` instances for
10−-- @cassava@'s encoding and decoding classes.
11−--
12−-- >>> type Eg = Get '[(CSV', MyEncodeOptions)] [(Int, String)]
13−--
14−-- Default encoding and decoding options are also provided, along with the
15−-- @CSV@ type synonym that uses them.
16−--
17−-- >>> type EgDefault = Get '[CSV] [(Int, String)]
18−module Servant.CSV.Cassava where
19−
20−#if !MIN_VERSION_base(4,8,0)
21−import Control.Applicative ((<$>))
22−#endif
23−import Data.Csv
24−import Data.Proxy (Proxy (..))
25−import Data.Typeable (Typeable)
26−import Data.Vector (Vector, toList)
27−import GHC.Generics (Generic)
28−import qualified Network.HTTP.Media as M
29−import Servant.API (Accept (..), MimeRender (..),
30− MimeUnrender (..))
31−
32−data CSV' deriving (Typeable, Generic)
33−
34−type CSV = (CSV', DefaultDecodeOpts)
35−
36−-- | @text/csv;charset=utf-8@
37−instance Accept (CSV', a) where
38− contentType _ = "text" M.// "csv" M./: ("charset", "utf-8")
39−
40−-- * Encoding
41−
42−-- ** Instances
43−
44−-- | Encode with 'encodeByNameWith'. The 'Header' param is used for determining
45−-- the order of headers and fields.
46−instance ( ToNamedRecord a, EncodeOpts opt
47− ) => MimeRender (CSV', opt) (Header, [a]) where
48− mimeRender _ (hdr, vals) = encodeByNameWith (encodeOpts p) hdr vals
49− where p = Proxy :: Proxy opt
50−
51−-- | Encode with 'encodeDefaultOrderedByNameWith'
52−instance ( DefaultOrdered a, ToNamedRecord a, EncodeOpts opt
53− ) => MimeRender (CSV', opt) [a] where
54− mimeRender _ = encodeDefaultOrderedByNameWith (encodeOpts p)
55− where p = Proxy :: Proxy opt
56−
57−-- | Encode with 'encodeByNameWith'. The 'Header' param is used for determining
58−-- the order of headers and fields.
59−instance ( ToNamedRecord a, EncodeOpts opt
60− ) => MimeRender (CSV', opt) (Header, Vector a) where
61− mimeRender _ (hdr, vals) = encodeByNameWith (encodeOpts p) hdr (toList vals)
62− where p = Proxy :: Proxy opt
63−
64−-- | Encode with 'encodeDefaultOrderedByNameWith'
65−instance ( DefaultOrdered a, ToNamedRecord a, EncodeOpts opt
66− ) => MimeRender (CSV', opt) (Vector a) where
67− mimeRender _ = encodeDefaultOrderedByNameWith (encodeOpts p) . toList
68− where p = Proxy :: Proxy opt
69−
70−-- ** Encode Options
71−
72−class EncodeOpts a where
73− encodeOpts :: Proxy a -> EncodeOptions
74−
75−data DefaultEncodeOpts deriving (Typeable, Generic)
76−
77−instance EncodeOpts DefaultEncodeOpts where
78− encodeOpts _ = defaultEncodeOptions
79−
80−-- * Decoding
81−
82−-- ** Instances
83−
84−-- | Decode with 'decodeByNameWith'
85−instance ( FromNamedRecord a, DecodeOpts opt
86− ) => MimeUnrender (CSV', opt) (Header, [a]) where
87− mimeUnrender _ bs = fmap toList <$> decodeByNameWith (decodeOpts p) bs
88− where p = Proxy :: Proxy opt
89−
90−-- | Decode with 'decodeWith'. Assumes data has headers, which are stripped.
91−instance ( FromRecord a, DecodeOpts opt
92− ) => MimeUnrender (CSV', opt) [a] where
93− mimeUnrender _ bs = toList <$> decodeWith (decodeOpts p) HasHeader bs
94− where p = Proxy :: Proxy opt
95−
96−instance ( FromNamedRecord a, DecodeOpts opt
97− ) => MimeUnrender (CSV', opt) (Header, Vector a) where
98− mimeUnrender _ = decodeByNameWith (decodeOpts p)
99− where p = Proxy :: Proxy opt
100−
101−-- | Decode with 'decodeWith'. Assumes data has headers, which are stripped.
102−instance ( FromRecord a, DecodeOpts opt
103− ) => MimeUnrender (CSV', opt) (Vector a) where
104− mimeUnrender _ = decodeWith (decodeOpts p) HasHeader
105− where p = Proxy :: Proxy opt
106−
107−-- ** Decode Options
108−
109−class DecodeOpts a where
110− decodeOpts :: Proxy a -> DecodeOptions
111−
112−data DefaultDecodeOpts deriving (Typeable, Generic)
113−
114−instance DecodeOpts DefaultDecodeOpts where
115− decodeOpts _ = defaultDecodeOptions
servant-cassava/tinc.yamldeleted−3
@@ -1,3 +0,0 @@
1−dependencies:
2− - name: servant
3− path: ../servant
servant-client/CHANGELOG.md+6
@@ -1,3 +1,9 @@
1+0.7.1
2+-----
3+
4+* Support GHC 8.0
5+* `ServantError` has an `Eq` instance now.
6+
170.6
28---
39
servant-client/README.md+3−4
@@ -13,9 +13,8 @@ type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
1313myApi :: Proxy MyApi
1414myApi = Proxy
1515
16−getAllBooks :: ExceptT String IO [Book]
17−postNewBook :: Book -> ExceptT String IO Book
16+getAllBooks :: Manager -> BaseUrl -> ExceptT String IO [Book]
17+postNewBook :: Book -> Manager -> BaseUrl -> ExceptT String IO Book
1818-- 'client' allows you to produce operations to query an API from a client.
19−(getAllBooks :<|> postNewBook) = client myApi host
20− where host = BaseUrl Http "localhost" 8080
19+(getAllBooks :<|> postNewBook) = client myApi
2120```
servant-client/servant-client.cabal+15−10
@@ -1,11 +1,11 @@
11name: servant-client
2−version: 0.6
2+version: 0.7.1
33synopsis: automatical derivation of querying functions for servant webservices
44description:
55 This library lets you derive automatically Haskell functions that
66 let you query each endpoint of a <http://hackage.haskell.org/package/servant servant> webservice.
77 .
8− See <http://haskell-servant.github.io/tutorial/client.html the client section of the tutorial>.
8+ See <http://haskell-servant.readthedocs.org/en/stable/tutorial/Client.html the client section of the tutorial>.
99 .
1010 <https://github.com/haskell-servant/servant/blob/master/servant-client/CHANGELOG.md CHANGELOG>
1111license: BSD3
@@ -15,11 +15,14 @@ maintainer: haskell-servant-maintainers@googlegroups.com
1515copyright: 2014-2016 Zalora South East Asia Pte Ltd, Servant Contributors
1616category: Web
1717build-type: Simple
18−extra-source-files: include/*.h
1918cabal-version: >=1.10
2019tested-with: GHC >= 7.8
21−homepage: http://haskell-servant.github.io/
20+homepage: http://haskell-servant.readthedocs.org/
2221Bug-reports: http://github.com/haskell-servant/servant/issues
22+extra-source-files:
23+ include/*.h
24+ CHANGELOG.md
25+ README.md
2326source-repository head
2427 type: git
2528 location: http://github.com/haskell-servant/servant.git
@@ -49,13 +52,13 @@ library
4952 , case-insensitive
5053 , exceptions
5154 , http-api-data >= 0.1 && < 0.3
52− , http-client
55+ , http-client <0.5
5356 , http-client-tls
5457 , http-media
5558 , http-types
5659 , network-uri >= 2.6
5760 , safe
58− , servant == 0.6.*
61+ , servant == 0.7.*
5962 , string-conversions
6063 , text
6164 , transformers
@@ -67,12 +70,13 @@ library
6770 hs-source-dirs: src
6871 default-language: Haskell2010
6972 ghc-options: -Wall
73+ if impl(ghc >= 8.0)
74+ ghc-options: -Wno-redundant-constraints
7075 include-dirs: include
7176
7277test-suite spec
7378 type: exitcode-stdio-1.0
74− ghc-options:
75− -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures
79+ ghc-options: -Wall -fno-warn-name-shadowing
7680 default-language: Haskell2010
7781 hs-source-dirs: test, src
7882 main-is: Spec.hs
@@ -92,6 +96,7 @@ test-suite spec
9296 Servant.Client.TestServer.GHC
9397 build-depends:
9498 base == 4.*
99+ , base-compat
95100 , transformers
96101 , transformers-compat
97102 , aeson
@@ -105,8 +110,8 @@ test-suite spec
105110 , HUnit
106111 , network >= 2.6
107112 , QuickCheck >= 2.7
108− , servant == 0.6.*
109− , servant-server == 0.6.*
113+ , servant == 0.7.*
114+ , servant-server == 0.7.*
110115 , text
111116 , wai
112117 , warp
servant-client/src/Servant/Client.hs+43−42
@@ -19,6 +19,7 @@ module Servant.Client
1919 , AuthenticateReq(..)
2020 , client
2121 , HasClient(..)
22+ , ClientM
2223 , mkAuthenticateReq
2324 , ServantError(..)
2425 , module Servant.Common.BaseUrl
@@ -57,15 +58,15 @@ import Servant.Client.PerformRequest (ServantError(..))
5758-- > getAllBooks :: Manager -> BaseUrl -> ClientM [Book]
5859-- > postNewBook :: Book -> Manager -> BaseUrl -> ClientM Book
5960-- > (getAllBooks :<|> postNewBook) = client myApi
60−client :: HasClient layout => Proxy layout -> Client layout
61+client :: HasClient api => Proxy api -> Client api
6162client p = clientWithRoute p defReq
6263
6364-- | This class lets us define how each API combinator
6465-- influences the creation of an HTTP request. It's mostly
6566-- an internal class, you can just use 'client'.
66−class HasClient layout where
67− type Client layout :: *
68− clientWithRoute :: Proxy layout -> Req -> Client layout
67+class HasClient api where
68+ type Client api :: *
69+ clientWithRoute :: Proxy api -> Req -> Client api
6970
7071
7172-- | A client querying function for @a ':<|>' b@ will actually hand you
@@ -106,14 +107,14 @@ instance (HasClient a, HasClient b) => HasClient (a :<|> b) where
106107-- > getBook :: Text -> Manager -> BaseUrl -> ClientM Book
107108-- > getBook = client myApi
108109-- > -- then you can just use "getBook" to query that endpoint
109−instance (KnownSymbol capture, ToHttpApiData a, HasClient sublayout)
110− => HasClient (Capture capture a :> sublayout) where
110+instance (KnownSymbol capture, ToHttpApiData a, HasClient api)
111+ => HasClient (Capture capture a :> api) where
111112
112− type Client (Capture capture a :> sublayout) =
113− a -> Client sublayout
113+ type Client (Capture capture a :> api) =
114+ a -> Client api
114115
115116 clientWithRoute Proxy req val =
116− clientWithRoute (Proxy :: Proxy sublayout)
117+ clientWithRoute (Proxy :: Proxy api)
117118 (appendToPath p req)
118119
119120 where p = unpack (toUrlPiece val)
@@ -186,14 +187,14 @@ instance OVERLAPPING_
186187-- > viewReferer = client myApi
187188-- > -- then you can just use "viewRefer" to query that endpoint
188189-- > -- specifying Nothing or e.g Just "http://haskell.org/" as arguments
189−instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
190− => HasClient (Header sym a :> sublayout) where
190+instance (KnownSymbol sym, ToHttpApiData a, HasClient api)
191+ => HasClient (Header sym a :> api) where
191192
192− type Client (Header sym a :> sublayout) =
193− Maybe a -> Client sublayout
193+ type Client (Header sym a :> api) =
194+ Maybe a -> Client api
194195
195196 clientWithRoute Proxy req mval =
196− clientWithRoute (Proxy :: Proxy sublayout)
197+ clientWithRoute (Proxy :: Proxy api)
197198 (maybe req
198199 (\value -> Servant.Common.Req.addHeader hname value req)
199200 mval
@@ -203,14 +204,14 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
203204
204205-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
205206-- functions.
206−instance HasClient sublayout
207− => HasClient (HttpVersion :> sublayout) where
207+instance HasClient api
208+ => HasClient (HttpVersion :> api) where
208209
209− type Client (HttpVersion :> sublayout) =
210− Client sublayout
210+ type Client (HttpVersion :> api) =
211+ Client api
211212
212213 clientWithRoute Proxy =
213− clientWithRoute (Proxy :: Proxy sublayout)
214+ clientWithRoute (Proxy :: Proxy api)
214215
215216-- | If you use a 'QueryParam' in one of your endpoints in your API,
216217-- the corresponding querying function will automatically take
@@ -237,15 +238,15 @@ instance HasClient sublayout
237238-- > -- then you can just use "getBooksBy" to query that endpoint.
238239-- > -- 'getBooksBy Nothing' for all books
239240-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
240−instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
241− => HasClient (QueryParam sym a :> sublayout) where
241+instance (KnownSymbol sym, ToHttpApiData a, HasClient api)
242+ => HasClient (QueryParam sym a :> api) where
242243
243− type Client (QueryParam sym a :> sublayout) =
244− Maybe a -> Client sublayout
244+ type Client (QueryParam sym a :> api) =
245+ Maybe a -> Client api
245246
246247 -- if mparam = Nothing, we don't add it to the query string
247248 clientWithRoute Proxy req mparam =
248− clientWithRoute (Proxy :: Proxy sublayout)
249+ clientWithRoute (Proxy :: Proxy api)
249250 (maybe req
250251 (flip (appendToQueryString pname) req . Just)
251252 mparamText
@@ -282,14 +283,14 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
282283-- > -- 'getBooksBy []' for all books
283284-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
284285-- > -- to get all books by Asimov and Heinlein
285−instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
286− => HasClient (QueryParams sym a :> sublayout) where
286+instance (KnownSymbol sym, ToHttpApiData a, HasClient api)
287+ => HasClient (QueryParams sym a :> api) where
287288
288− type Client (QueryParams sym a :> sublayout) =
289− [a] -> Client sublayout
289+ type Client (QueryParams sym a :> api) =
290+ [a] -> Client api
290291
291292 clientWithRoute Proxy req paramlist =
292− clientWithRoute (Proxy :: Proxy sublayout)
293+ clientWithRoute (Proxy :: Proxy api)
293294 (foldl' (\ req' -> maybe req' (flip (appendToQueryString pname) req' . Just))
294295 req
295296 paramlist'
@@ -320,14 +321,14 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
320321-- > -- then you can just use "getBooks" to query that endpoint.
321322-- > -- 'getBooksBy False' for all books
322323-- > -- 'getBooksBy True' to only get _already published_ books
323−instance (KnownSymbol sym, HasClient sublayout)
324− => HasClient (QueryFlag sym :> sublayout) where
324+instance (KnownSymbol sym, HasClient api)
325+ => HasClient (QueryFlag sym :> api) where
325326
326− type Client (QueryFlag sym :> sublayout) =
327− Bool -> Client sublayout
327+ type Client (QueryFlag sym :> api) =
328+ Bool -> Client api
328329
329330 clientWithRoute Proxy req flag =
330− clientWithRoute (Proxy :: Proxy sublayout)
331+ clientWithRoute (Proxy :: Proxy api)
331332 (if flag
332333 then appendToQueryString paramname Nothing req
333334 else req
@@ -364,14 +365,14 @@ instance HasClient Raw where
364365-- > addBook :: Book -> Manager -> BaseUrl -> ClientM Book
365366-- > addBook = client myApi
366367-- > -- then you can just use "addBook" to query that endpoint
367−instance (MimeRender ct a, HasClient sublayout)
368− => HasClient (ReqBody (ct ': cts) a :> sublayout) where
368+instance (MimeRender ct a, HasClient api)
369+ => HasClient (ReqBody (ct ': cts) a :> api) where
369370
370− type Client (ReqBody (ct ': cts) a :> sublayout) =
371− a -> Client sublayout
371+ type Client (ReqBody (ct ': cts) a :> api) =
372+ a -> Client api
372373
373374 clientWithRoute Proxy req body =
374− clientWithRoute (Proxy :: Proxy sublayout)
375+ clientWithRoute (Proxy :: Proxy api)
375376 (let ctProxy = Proxy :: Proxy ct
376377 in setRQBody (mimeRender ctProxy body)
377378 (contentType ctProxy)
@@ -379,11 +380,11 @@ instance (MimeRender ct a, HasClient sublayout)
379380 )
380381
381382-- | Make the querying function append @path@ to the request path.
382−instance (KnownSymbol path, HasClient sublayout) => HasClient (path :> sublayout) where
383− type Client (path :> sublayout) = Client sublayout
383+instance (KnownSymbol path, HasClient api) => HasClient (path :> api) where
384+ type Client (path :> api) = Client api
384385
385386 clientWithRoute Proxy req =
386− clientWithRoute (Proxy :: Proxy sublayout)
387+ clientWithRoute (Proxy :: Proxy api)
387388 (appendToPath p req)
388389
389390 where p = symbolVal (Proxy :: Proxy path)
servant-client/src/Servant/Client/PerformRequest/Base.hs+13
@@ -32,4 +32,17 @@ data ServantError
3232 }
3333 deriving (Show, Typeable)
3434
35+instance Eq ServantError where
36+ FailureResponse a b c == FailureResponse x y z =
37+ (a, b, c) == (x, y, z)
38+ DecodeFailure a b c == DecodeFailure x y z =
39+ (a, b, c) == (x, y, z)
40+ UnsupportedContentType a b == UnsupportedContentType x y =
41+ (a, b) == (x, y)
42+ InvalidContentTypeHeader a b == InvalidContentTypeHeader x y =
43+ (a, b) == (x, y)
44+ ConnectionError a == ConnectionError x =
45+ show a == show x
46+ _ == _ = False
47+
3548instance Exception ServantError
servant-client/src/Servant/Common/Req.hs+9−1
@@ -2,6 +2,11 @@
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE OverloadedStrings #-}
44{-# LANGUAGE ScopedTypeVariables #-}
5+
6+#if MIN_VERSION_base(4,9,0)
7+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
8+#endif
9+
510module Servant.Common.Req where
611
712#if !MIN_VERSION_base(4,8,0)
@@ -63,7 +68,7 @@ setRQBody b t req = req { reqBody = Just (b, t) }
6368
6469reqToRequest :: (Functor m, MonadThrow m) => Req -> BaseUrl -> m Request
6570reqToRequest req (BaseUrl reqScheme reqHost reqPort path) =
66− setheaders . setAccept . setrqb . setQS <$> parseUrl url
71+ setheaders . setAccept . setrqb . setQS <$> parseUrlThrow url
6772
6873 where url = show $ nullURI { uriScheme = case reqScheme of
6974 Http -> "http:"
@@ -89,6 +94,9 @@ reqToRequest req (BaseUrl reqScheme reqHost reqPort path) =
8994 | not . null . reqAccept $ req] }
9095 toProperHeader (name, val) =
9196 (fromString name, encodeUtf8 val)
97+#if !MIN_VERSION_http_client(0,4,30)
98+ parseUrlThrow = parseUrl
99+#endif
92100
93101
94102-- * performing requests
servant-client/test/Servant/ClientSpec.hs+8−5
@@ -13,14 +13,18 @@
1313{-# LANGUAGE TypeFamilies #-}
1414{-# LANGUAGE TypeOperators #-}
1515{-# LANGUAGE UndecidableInstances #-}
16+#if __GLASGOW_HASKELL__ >= 800
17+{-# OPTIONS_GHC -freduction-depth=100 #-}
18+#else
1619{-# OPTIONS_GHC -fcontext-stack=100 #-}
20+#endif
1721{-# OPTIONS_GHC -fno-warn-orphans #-}
1822
1923module Servant.ClientSpec where
2024
21−#if !MIN_VERSION_base(4,8,0)
22−import Control.Applicative ((<$>))
23−#endif
25+import Prelude ()
26+import Prelude.Compat
27+
2428import Control.Arrow (left)
2529import Control.Monad.Trans.Except (runExceptT, throwE)
2630import Data.Aeson
@@ -36,7 +40,7 @@ import Network.HTTP.Media
3640import qualified Network.HTTP.Types as HTTP
3741import Network.Wai (responseLBS)
3842import qualified Network.Wai as Wai
39−import System.Exit
43+import System.Exit.Compat
4044import System.IO.Unsafe (unsafePerformIO)
4145import Test.HUnit
4246import Test.Hspec
@@ -432,7 +436,6 @@ failSpec = around (withTestServer "failServer") $ do
432436 InvalidContentTypeHeader "fooooo" _ -> return ()
433437 _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
434438
435−
436439-- * utils
437440
438441pathGen :: Gen (NonEmptyList Char)
servant-docs/CHANGELOG.md+10
@@ -1,3 +1,13 @@
1+0.7.1
2+-----
3+
4+* Support GHC 8.0
5+
6+0.7
7+---
8+
9+* Use `throwError` instead of `throwE` in documentation
10+
1110.5
212----
313
servant-docs/servant-docs.cabal+5−4
@@ -1,5 +1,5 @@
11name: servant-docs
2−version: 0.6
2+version: 0.7.1
33synopsis: generate API docs for your servant webservice
44description:
55 Library for generating API docs from a servant API definition.
@@ -16,7 +16,7 @@ category: Web
1616build-type: Simple
1717cabal-version: >=1.10
1818tested-with: GHC >= 7.8
19−homepage: http://haskell-servant.github.io/
19+homepage: http://haskell-servant.readthedocs.org/
2020Bug-reports: http://github.com/haskell-servant/servant/issues
2121extra-source-files:
2222 include/*.h
@@ -42,7 +42,7 @@ library
4242 , http-media >= 0.6
4343 , http-types >= 0.7
4444 , lens
45− , servant == 0.6.*
45+ , servant == 0.7.*
4646 , string-conversions
4747 , text
4848 , unordered-containers
@@ -50,6 +50,8 @@ library
5050 hs-source-dirs: src
5151 default-language: Haskell2010
5252 ghc-options: -Wall
53+ if impl(ghc >= 8.0)
54+ ghc-options: -Wno-redundant-constraints
5355 include-dirs: include
5456
5557executable greet-docs
@@ -82,4 +84,3 @@ test-suite spec
8284 , servant-docs
8385 , string-conversions
8486 default-language: Haskell2010
85−
servant-docs/src/Servant/Docs/Internal.hs+53−53
@@ -163,7 +163,7 @@ data DocNote = DocNote
163163--
164164-- These are intended to be built using extraInfo.
165165-- Multiple ExtraInfo may be combined with the monoid instance.
166−newtype ExtraInfo layout = ExtraInfo (HashMap Endpoint Action)
166+newtype ExtraInfo api = ExtraInfo (HashMap Endpoint Action)
167167instance Monoid (ExtraInfo a) where
168168 mempty = ExtraInfo mempty
169169 ExtraInfo a `mappend` ExtraInfo b =
@@ -300,11 +300,11 @@ makeLenses ''Action
300300-- default way to create documentation.
301301--
302302-- prop> docs == docsWithOptions defaultDocOptions
303−docs :: HasDocs layout => Proxy layout -> API
303+docs :: HasDocs api => Proxy api -> API
304304docs p = docsWithOptions p defaultDocOptions
305305
306306-- | Generate the docs for a given API that implements 'HasDocs'.
307−docsWithOptions :: HasDocs layout => Proxy layout -> DocOptions -> API
307+docsWithOptions :: HasDocs api => Proxy api -> DocOptions -> API
308308docsWithOptions p = docsFor p (defEndpoint, defAction)
309309
310310-- | Closed type family, check if endpoint is exactly within API.
@@ -316,7 +316,7 @@ type family IsIn (endpoint :: *) (api :: *) :: Constraint where
316316 IsIn (e :> sa) (e :> sb) = IsIn sa sb
317317 IsIn e e = ()
318318
319−-- | Create an 'ExtraInfo' that is garunteed to be within the given API layout.
319+-- | Create an 'ExtraInfo' that is guaranteed to be within the given API layout.
320320--
321321-- The safety here is to ensure that you only add custom documentation to an
322322-- endpoint that actually exists within your API.
@@ -329,8 +329,8 @@ type family IsIn (endpoint :: *) (api :: *) :: Constraint where
329329-- > , DocNote "Second secton" ["And some more"]
330330-- > ]
331331
332−extraInfo :: (IsIn endpoint layout, HasLink endpoint, HasDocs endpoint)
333− => Proxy endpoint -> Action -> ExtraInfo layout
332+extraInfo :: (IsIn endpoint api, HasLink endpoint, HasDocs endpoint)
333+ => Proxy endpoint -> Action -> ExtraInfo api
334334extraInfo p action =
335335 let api = docsFor p (defEndpoint, defAction) defaultDocOptions
336336 -- Assume one endpoint, HasLink constraint means that we should only ever
@@ -349,7 +349,7 @@ extraInfo p action =
349349-- 'extraInfo'.
350350--
351351-- If you only want to add an introduction, use 'docsWithIntros'.
352−docsWith :: HasDocs layout => DocOptions -> [DocIntro] -> ExtraInfo layout -> Proxy layout -> API
352+docsWith :: HasDocs api => DocOptions -> [DocIntro] -> ExtraInfo api -> Proxy api -> API
353353docsWith opts intros (ExtraInfo endpoints) p =
354354 docsWithOptions p opts
355355 & apiIntros <>~ intros
@@ -358,13 +358,13 @@ docsWith opts intros (ExtraInfo endpoints) p =
358358
359359-- | Generate the docs for a given API that implements 'HasDocs' with with any
360360-- number of introduction(s)
361−docsWithIntros :: HasDocs layout => [DocIntro] -> Proxy layout -> API
361+docsWithIntros :: HasDocs api => [DocIntro] -> Proxy api -> API
362362docsWithIntros intros = docsWith defaultDocOptions intros mempty
363363
364364-- | The class that abstracts away the impact of API combinators
365365-- on documentation generation.
366−class HasDocs layout where
367− docsFor :: Proxy layout -> (Endpoint, Action) -> DocOptions -> API
366+class HasDocs api where
367+ docsFor :: Proxy api -> (Endpoint, Action) -> DocOptions -> API
368368
369369-- | The class that lets us display a sample input or output in the supported
370370-- content-types when generating documentation for endpoints that either:
@@ -675,26 +675,26 @@ markdown api = unlines $
675675-- | The generated docs for @a ':<|>' b@ just appends the docs
676676-- for @a@ with the docs for @b@.
677677instance OVERLAPPABLE_
678− (HasDocs layout1, HasDocs layout2)
679− => HasDocs (layout1 :<|> layout2) where
678+ (HasDocs a, HasDocs b)
679+ => HasDocs (a :<|> b) where
680680
681681 docsFor Proxy (ep, action) = docsFor p1 (ep, action) <> docsFor p2 (ep, action)
682682
683− where p1 :: Proxy layout1
683+ where p1 :: Proxy a
684684 p1 = Proxy
685685
686− p2 :: Proxy layout2
686+ p2 :: Proxy b
687687 p2 = Proxy
688688
689689-- | @"books" :> 'Capture' "isbn" Text@ will appear as
690690-- @/books/:isbn@ in the docs.
691−instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs sublayout)
692− => HasDocs (Capture sym a :> sublayout) where
691+instance (KnownSymbol sym, ToCapture (Capture sym a), HasDocs api)
692+ => HasDocs (Capture sym a :> api) where
693693
694694 docsFor Proxy (endpoint, action) =
695− docsFor sublayoutP (endpoint', action')
695+ docsFor subApiP (endpoint', action')
696696
697− where sublayoutP = Proxy :: Proxy sublayout
697+ where subApiP = Proxy :: Proxy api
698698 captureP = Proxy :: Proxy (Capture sym a)
699699
700700 action' = over captures (|> toCapture captureP) action
@@ -736,43 +736,43 @@ instance OVERLAPPING_
736736 status = fromInteger $ natVal (Proxy :: Proxy status)
737737 p = Proxy :: Proxy a
738738
739−instance (KnownSymbol sym, HasDocs sublayout)
740− => HasDocs (Header sym a :> sublayout) where
739+instance (KnownSymbol sym, HasDocs api)
740+ => HasDocs (Header sym a :> api) where
741741 docsFor Proxy (endpoint, action) =
742− docsFor sublayoutP (endpoint, action')
742+ docsFor subApiP (endpoint, action')
743743
744− where sublayoutP = Proxy :: Proxy sublayout
744+ where subApiP = Proxy :: Proxy api
745745 action' = over headers (|> headername) action
746746 headername = T.pack $ symbolVal (Proxy :: Proxy sym)
747747
748−instance (KnownSymbol sym, ToParam (QueryParam sym a), HasDocs sublayout)
749− => HasDocs (QueryParam sym a :> sublayout) where
748+instance (KnownSymbol sym, ToParam (QueryParam sym a), HasDocs api)
749+ => HasDocs (QueryParam sym a :> api) where
750750
751751 docsFor Proxy (endpoint, action) =
752− docsFor sublayoutP (endpoint, action')
752+ docsFor subApiP (endpoint, action')
753753
754− where sublayoutP = Proxy :: Proxy sublayout
754+ where subApiP = Proxy :: Proxy api
755755 paramP = Proxy :: Proxy (QueryParam sym a)
756756 action' = over params (|> toParam paramP) action
757757
758−instance (KnownSymbol sym, ToParam (QueryParams sym a), HasDocs sublayout)
759− => HasDocs (QueryParams sym a :> sublayout) where
758+instance (KnownSymbol sym, ToParam (QueryParams sym a), HasDocs api)
759+ => HasDocs (QueryParams sym a :> api) where
760760
761761 docsFor Proxy (endpoint, action) =
762− docsFor sublayoutP (endpoint, action')
762+ docsFor subApiP (endpoint, action')
763763
764− where sublayoutP = Proxy :: Proxy sublayout
764+ where subApiP = Proxy :: Proxy api
765765 paramP = Proxy :: Proxy (QueryParams sym a)
766766 action' = over params (|> toParam paramP) action
767767
768768
769−instance (KnownSymbol sym, ToParam (QueryFlag sym), HasDocs sublayout)
770− => HasDocs (QueryFlag sym :> sublayout) where
769+instance (KnownSymbol sym, ToParam (QueryFlag sym), HasDocs api)
770+ => HasDocs (QueryFlag sym :> api) where
771771
772772 docsFor Proxy (endpoint, action) =
773− docsFor sublayoutP (endpoint, action')
773+ docsFor subApiP (endpoint, action')
774774
775− where sublayoutP = Proxy :: Proxy sublayout
775+ where subApiP = Proxy :: Proxy api
776776 paramP = Proxy :: Proxy (QueryFlag sym)
777777 action' = over params (|> toParam paramP) action
778778
@@ -785,49 +785,49 @@ instance HasDocs Raw where
785785-- example data. However, there's no reason to believe that the instances of
786786-- 'AllMimeUnrender' and 'AllMimeRender' actually agree (or to suppose that
787787-- both are even defined) for any particular type.
788−instance (ToSample a, AllMimeRender (ct ': cts) a, HasDocs sublayout)
789− => HasDocs (ReqBody (ct ': cts) a :> sublayout) where
788+instance (ToSample a, AllMimeRender (ct ': cts) a, HasDocs api)
789+ => HasDocs (ReqBody (ct ': cts) a :> api) where
790790
791791 docsFor Proxy (endpoint, action) =
792− docsFor sublayoutP (endpoint, action')
792+ docsFor subApiP (endpoint, action')
793793
794− where sublayoutP = Proxy :: Proxy sublayout
794+ where subApiP = Proxy :: Proxy api
795795 action' = action & rqbody .~ sampleByteString t p
796796 & rqtypes .~ allMime t
797797 t = Proxy :: Proxy (ct ': cts)
798798 p = Proxy :: Proxy a
799799
800−instance (KnownSymbol path, HasDocs sublayout) => HasDocs (path :> sublayout) where
800+instance (KnownSymbol path, HasDocs api) => HasDocs (path :> api) where
801801
802802 docsFor Proxy (endpoint, action) =
803− docsFor sublayoutP (endpoint', action)
803+ docsFor subApiP (endpoint', action)
804804
805− where sublayoutP = Proxy :: Proxy sublayout
805+ where subApiP = Proxy :: Proxy api
806806 endpoint' = endpoint & path <>~ [symbolVal pa]
807807 pa = Proxy :: Proxy path
808808
809−instance HasDocs sublayout => HasDocs (RemoteHost :> sublayout) where
809+instance HasDocs api => HasDocs (RemoteHost :> api) where
810810 docsFor Proxy ep =
811− docsFor (Proxy :: Proxy sublayout) ep
811+ docsFor (Proxy :: Proxy api) ep
812812
813−instance HasDocs sublayout => HasDocs (IsSecure :> sublayout) where
813+instance HasDocs api => HasDocs (IsSecure :> api) where
814814 docsFor Proxy ep =
815− docsFor (Proxy :: Proxy sublayout) ep
815+ docsFor (Proxy :: Proxy api) ep
816816
817−instance HasDocs sublayout => HasDocs (HttpVersion :> sublayout) where
817+instance HasDocs api => HasDocs (HttpVersion :> api) where
818818 docsFor Proxy ep =
819− docsFor (Proxy :: Proxy sublayout) ep
819+ docsFor (Proxy :: Proxy api) ep
820820
821−instance HasDocs sublayout => HasDocs (Vault :> sublayout) where
821+instance HasDocs api => HasDocs (Vault :> api) where
822822 docsFor Proxy ep =
823− docsFor (Proxy :: Proxy sublayout) ep
823+ docsFor (Proxy :: Proxy api) ep
824824
825−instance HasDocs sublayout => HasDocs (WithNamedContext name context sublayout) where
826− docsFor Proxy = docsFor (Proxy :: Proxy sublayout)
825+instance HasDocs api => HasDocs (WithNamedContext name context api) where
826+ docsFor Proxy = docsFor (Proxy :: Proxy api)
827827
828−instance (ToAuthInfo (BasicAuth realm usr), HasDocs sublayout) => HasDocs (BasicAuth realm usr :> sublayout) where
828+instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth realm usr :> api) where
829829 docsFor Proxy (endpoint, action) =
830− docsFor (Proxy :: Proxy sublayout) (endpoint, action')
830+ docsFor (Proxy :: Proxy api) (endpoint, action')
831831 where
832832 authProxy = Proxy :: Proxy (BasicAuth realm usr)
833833 action' = over authInfo (|> toAuthInfo authProxy) action
servant-docs/src/Servant/Docs/Internal/Pretty.hs+2−2
@@ -29,12 +29,12 @@ instance ToJSON a => MimeRender PrettyJSON a where
2929-- @
3030-- 'docs' ('pretty' ('Proxy' :: 'Proxy' MyAPI))
3131-- @
32−pretty :: Proxy layout -> Proxy (Pretty layout)
32+pretty :: Proxy api -> Proxy (Pretty api)
3333pretty Proxy = Proxy
3434
3535-- | Replace all JSON content types with PrettyJSON.
3636-- Kind-polymorphic so it can operate on kinds @*@ and @[*]@.
37−type family Pretty (layout :: k) :: k where
37+type family Pretty (api :: k) :: k where
3838 Pretty (x :<|> y) = Pretty x :<|> Pretty y
3939 Pretty (x :> y) = Pretty x :> Pretty y
4040 Pretty (Get cs r) = Get (Pretty cs) r
servant-foreign/CHANGELOG.md+5
@@ -1,3 +1,8 @@
1+0.7.1
2+-----
3+
4+* Support GHC 8.0
5+
160.5
27-----
38* Use the `text` package instead of `String`.
servant-foreign/servant-foreign.cabal+5−2
@@ -1,5 +1,5 @@
11name: servant-foreign
2−version: 0.6
2+version: 0.7.1
33synopsis: Helpers for generating clients for servant APIs in any programming language
44description:
55 Helper types and functions for generating client functions for servant APIs in any programming language
@@ -21,6 +21,7 @@ extra-source-files:
2121 include/*.h
2222 CHANGELOG.md
2323 README.md
24+bug-reports: http://github.com/haskell-servant/servant/issues
2425source-repository head
2526 type: git
2627 location: http://github.com/haskell-servant/servant.git
@@ -31,12 +32,14 @@ library
3132 , Servant.Foreign.Inflections
3233 build-depends: base == 4.*
3334 , lens == 4.*
34− , servant == 0.6.*
35+ , servant == 0.7.*
3536 , text >= 1.2 && < 1.3
3637 , http-types
3738 hs-source-dirs: src
3839 default-language: Haskell2010
3940 ghc-options: -Wall
41+ if impl(ghc >= 8.0)
42+ ghc-options: -Wno-redundant-constraints
4043 include-dirs: include
4144 default-extensions: CPP
4245 , ConstraintKinds
servant-foreign/src/Servant/Foreign/Internal.hs+52−51
@@ -7,7 +7,8 @@
77-- arbitrary programming languages.
88module Servant.Foreign.Internal where
99
10−import Control.Lens hiding (cons, List)
10+import Control.Lens (makePrisms, makeLenses, Getter, (&), (<>~), (%~),
11+ (.~))
1112#if !MIN_VERSION_base(4,8,0)
1213import Data.Monoid
1314#endif
@@ -183,9 +184,9 @@ data NoTypes
183184instance HasForeignType NoTypes () ftype where
184185 typeFor _ _ _ = ()
185186
186−class HasForeign lang ftype (layout :: *) where
187− type Foreign ftype layout :: *
188− foreignFor :: Proxy lang -> Proxy ftype -> Proxy layout -> Req ftype -> Foreign ftype layout
187+class HasForeign lang ftype (api :: *) where
188+ type Foreign ftype api :: *
189+ foreignFor :: Proxy lang -> Proxy ftype -> Proxy api -> Req ftype -> Foreign ftype api
189190
190191instance (HasForeign lang ftype a, HasForeign lang ftype b)
191192 => HasForeign lang ftype (a :<|> b) where
@@ -195,12 +196,12 @@ instance (HasForeign lang ftype a, HasForeign lang ftype b)
195196 foreignFor lang ftype (Proxy :: Proxy a) req
196197 :<|> foreignFor lang ftype (Proxy :: Proxy b) req
197198
198−instance (KnownSymbol sym, HasForeignType lang ftype t, HasForeign lang ftype sublayout)
199− => HasForeign lang ftype (Capture sym t :> sublayout) where
200− type Foreign ftype (Capture sym a :> sublayout) = Foreign ftype sublayout
199+instance (KnownSymbol sym, HasForeignType lang ftype t, HasForeign lang ftype api)
200+ => HasForeign lang ftype (Capture sym t :> api) where
201+ type Foreign ftype (Capture sym a :> api) = Foreign ftype api
201202
202203 foreignFor lang Proxy Proxy req =
203− foreignFor lang Proxy (Proxy :: Proxy sublayout) $
204+ foreignFor lang Proxy (Proxy :: Proxy api) $
204205 req & reqUrl . path <>~ [Segment (Cap arg)]
205206 & reqFuncName . _FunctionName %~ (++ ["by", str])
206207 where
@@ -223,9 +224,9 @@ instance (Elem JSON list, HasForeignType lang ftype a, ReflectMethod method)
223224 method = reflectMethod (Proxy :: Proxy method)
224225 methodLC = toLower $ decodeUtf8 method
225226
226−instance (KnownSymbol sym, HasForeignType lang ftype a, HasForeign lang ftype sublayout)
227− => HasForeign lang ftype (Header sym a :> sublayout) where
228− type Foreign ftype (Header sym a :> sublayout) = Foreign ftype sublayout
227+instance (KnownSymbol sym, HasForeignType lang ftype a, HasForeign lang ftype api)
228+ => HasForeign lang ftype (Header sym a :> api) where
229+ type Foreign ftype (Header sym a :> api) = Foreign ftype api
229230
230231 foreignFor lang Proxy Proxy req =
231232 foreignFor lang Proxy subP $ req & reqHeaders <>~ [HeaderArg arg]
@@ -234,14 +235,14 @@ instance (KnownSymbol sym, HasForeignType lang ftype a, HasForeign lang ftype su
234235 arg = Arg
235236 { _argName = PathSegment hname
236237 , _argType = typeFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy a) }
237− subP = Proxy :: Proxy sublayout
238+ subP = Proxy :: Proxy api
238239
239−instance (KnownSymbol sym, HasForeignType lang ftype a, HasForeign lang ftype sublayout)
240− => HasForeign lang ftype (QueryParam sym a :> sublayout) where
241− type Foreign ftype (QueryParam sym a :> sublayout) = Foreign ftype sublayout
240+instance (KnownSymbol sym, HasForeignType lang ftype a, HasForeign lang ftype api)
241+ => HasForeign lang ftype (QueryParam sym a :> api) where
242+ type Foreign ftype (QueryParam sym a :> api) = Foreign ftype api
242243
243244 foreignFor lang Proxy Proxy req =
244− foreignFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy sublayout) $
245+ foreignFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy api) $
245246 req & reqUrl.queryStr <>~ [QueryArg arg Normal]
246247 where
247248 str = pack . symbolVal $ (Proxy :: Proxy sym)
@@ -250,11 +251,11 @@ instance (KnownSymbol sym, HasForeignType lang ftype a, HasForeign lang ftype su
250251 , _argType = typeFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy a) }
251252
252253instance
253− (KnownSymbol sym, HasForeignType lang ftype [a], HasForeign lang ftype sublayout)
254− => HasForeign lang ftype (QueryParams sym a :> sublayout) where
255− type Foreign ftype (QueryParams sym a :> sublayout) = Foreign ftype sublayout
254+ (KnownSymbol sym, HasForeignType lang ftype [a], HasForeign lang ftype api)
255+ => HasForeign lang ftype (QueryParams sym a :> api) where
256+ type Foreign ftype (QueryParams sym a :> api) = Foreign ftype api
256257 foreignFor lang Proxy Proxy req =
257− foreignFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy sublayout) $
258+ foreignFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy api) $
258259 req & reqUrl.queryStr <>~ [QueryArg arg List]
259260 where
260261 str = pack . symbolVal $ (Proxy :: Proxy sym)
@@ -263,12 +264,12 @@ instance
263264 , _argType = typeFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy [a]) }
264265
265266instance
266− (KnownSymbol sym, HasForeignType lang ftype Bool, HasForeign lang ftype sublayout)
267− => HasForeign lang ftype (QueryFlag sym :> sublayout) where
268− type Foreign ftype (QueryFlag sym :> sublayout) = Foreign ftype sublayout
267+ (KnownSymbol sym, HasForeignType lang ftype Bool, HasForeign lang ftype api)
268+ => HasForeign lang ftype (QueryFlag sym :> api) where
269+ type Foreign ftype (QueryFlag sym :> api) = Foreign ftype api
269270
270271 foreignFor lang ftype Proxy req =
271− foreignFor lang ftype (Proxy :: Proxy sublayout) $
272+ foreignFor lang ftype (Proxy :: Proxy api) $
272273 req & reqUrl.queryStr <>~ [QueryArg arg Flag]
273274 where
274275 str = pack . symbolVal $ (Proxy :: Proxy sym)
@@ -283,20 +284,20 @@ instance HasForeign lang ftype Raw where
283284 req & reqFuncName . _FunctionName %~ ((toLower $ decodeUtf8 method) :)
284285 & reqMethod .~ method
285286
286−instance (Elem JSON list, HasForeignType lang ftype a, HasForeign lang ftype sublayout)
287− => HasForeign lang ftype (ReqBody list a :> sublayout) where
288− type Foreign ftype (ReqBody list a :> sublayout) = Foreign ftype sublayout
287+instance (Elem JSON list, HasForeignType lang ftype a, HasForeign lang ftype api)
288+ => HasForeign lang ftype (ReqBody list a :> api) where
289+ type Foreign ftype (ReqBody list a :> api) = Foreign ftype api
289290
290291 foreignFor lang ftype Proxy req =
291− foreignFor lang ftype (Proxy :: Proxy sublayout) $
292+ foreignFor lang ftype (Proxy :: Proxy api) $
292293 req & reqBody .~ (Just $ typeFor lang ftype (Proxy :: Proxy a))
293294
294−instance (KnownSymbol path, HasForeign lang ftype sublayout)
295− => HasForeign lang ftype (path :> sublayout) where
296− type Foreign ftype (path :> sublayout) = Foreign ftype sublayout
295+instance (KnownSymbol path, HasForeign lang ftype api)
296+ => HasForeign lang ftype (path :> api) where
297+ type Foreign ftype (path :> api) = Foreign ftype api
297298
298299 foreignFor lang ftype Proxy req =
299− foreignFor lang ftype (Proxy :: Proxy sublayout) $
300+ foreignFor lang ftype (Proxy :: Proxy api) $
300301 req & reqUrl . path <>~ [Segment (Static (PathSegment str))]
301302 & reqFuncName . _FunctionName %~ (++ [str])
302303 where
@@ -304,39 +305,39 @@ instance (KnownSymbol path, HasForeign lang ftype sublayout)
304305 Data.Text.map (\c -> if c == '.' then '_' else c)
305306 . pack . symbolVal $ (Proxy :: Proxy path)
306307
307−instance HasForeign lang ftype sublayout
308− => HasForeign lang ftype (RemoteHost :> sublayout) where
309− type Foreign ftype (RemoteHost :> sublayout) = Foreign ftype sublayout
308+instance HasForeign lang ftype api
309+ => HasForeign lang ftype (RemoteHost :> api) where
310+ type Foreign ftype (RemoteHost :> api) = Foreign ftype api
310311
311312 foreignFor lang ftype Proxy req =
312− foreignFor lang ftype (Proxy :: Proxy sublayout) req
313+ foreignFor lang ftype (Proxy :: Proxy api) req
313314
314−instance HasForeign lang ftype sublayout
315− => HasForeign lang ftype (IsSecure :> sublayout) where
316− type Foreign ftype (IsSecure :> sublayout) = Foreign ftype sublayout
315+instance HasForeign lang ftype api
316+ => HasForeign lang ftype (IsSecure :> api) where
317+ type Foreign ftype (IsSecure :> api) = Foreign ftype api
317318
318319 foreignFor lang ftype Proxy req =
319− foreignFor lang ftype (Proxy :: Proxy sublayout) req
320+ foreignFor lang ftype (Proxy :: Proxy api) req
320321
321−instance HasForeign lang ftype sublayout => HasForeign lang ftype (Vault :> sublayout) where
322− type Foreign ftype (Vault :> sublayout) = Foreign ftype sublayout
322+instance HasForeign lang ftype api => HasForeign lang ftype (Vault :> api) where
323+ type Foreign ftype (Vault :> api) = Foreign ftype api
323324
324325 foreignFor lang ftype Proxy req =
325− foreignFor lang ftype (Proxy :: Proxy sublayout) req
326+ foreignFor lang ftype (Proxy :: Proxy api) req
326327
327−instance HasForeign lang ftype sublayout =>
328− HasForeign lang ftype (WithNamedContext name context sublayout) where
328+instance HasForeign lang ftype api =>
329+ HasForeign lang ftype (WithNamedContext name context api) where
329330
330− type Foreign ftype (WithNamedContext name context sublayout) = Foreign ftype sublayout
331+ type Foreign ftype (WithNamedContext name context api) = Foreign ftype api
331332
332− foreignFor lang ftype Proxy = foreignFor lang ftype (Proxy :: Proxy sublayout)
333+ foreignFor lang ftype Proxy = foreignFor lang ftype (Proxy :: Proxy api)
333334
334−instance HasForeign lang ftype sublayout
335− => HasForeign lang ftype (HttpVersion :> sublayout) where
336− type Foreign ftype (HttpVersion :> sublayout) = Foreign ftype sublayout
335+instance HasForeign lang ftype api
336+ => HasForeign lang ftype (HttpVersion :> api) where
337+ type Foreign ftype (HttpVersion :> api) = Foreign ftype api
337338
338339 foreignFor lang ftype Proxy req =
339− foreignFor lang ftype (Proxy :: Proxy sublayout) req
340+ foreignFor lang ftype (Proxy :: Proxy api) req
340341
341342-- | Utility class used by 'listFromAPI' which computes
342343-- the data needed to generate a function for each endpoint
servant-js/servant-js.cabal+6−6
@@ -1,5 +1,5 @@
11name: servant-js
2−version: 0.6
2+version: 0.7.1
33synopsis: Automatically derive javascript functions to query servant webservices.
44description:
55 Automatically derive javascript functions to query servant webservices.
@@ -19,7 +19,7 @@ copyright: 2015-2016 Servant Contributors
1919category: Web
2020build-type: Simple
2121cabal-version: >=1.10
22−homepage: http://haskell-servant.github.io/
22+homepage: http://haskell-servant.readthedocs.org/
2323Bug-reports: http://github.com/haskell-servant/servant/issues
2424extra-source-files:
2525 include/*.h
@@ -45,7 +45,7 @@ library
4545 , base-compat >= 0.9
4646 , charset >= 0.3
4747 , lens >= 4
48− , servant-foreign == 0.6.*
48+ , servant-foreign == 0.7.*
4949 , text >= 1.2 && < 1.3
5050
5151 hs-source-dirs: src
@@ -55,7 +55,7 @@ library
5555
5656executable counter
5757 main-is: counter.hs
58− ghc-options: -O2 -Wall
58+ ghc-options: -Wall
5959 hs-source-dirs: examples
6060
6161 if flag(example)
@@ -67,8 +67,8 @@ executable counter
6767 , aeson >= 0.7 && < 0.12
6868 , filepath >= 1
6969 , lens >= 4
70− , servant == 0.6.*
71− , servant-server == 0.6.*
70+ , servant == 0.7.*
71+ , servant-server == 0.7.*
7272 , servant-js
7373 , stm
7474 , transformers
servant-js/src/Servant/JS.hs+2−2
@@ -123,12 +123,12 @@ import Servant.JS.Axios
123123import Servant.JS.Internal
124124import Servant.JS.JQuery
125125import Servant.JS.Vanilla
126−import Servant.Foreign (GenerateList(..), listFromAPI, NoTypes)
126+import Servant.Foreign (listFromAPI)
127127
128128-- | Generate the data necessary to generate javascript code
129129-- for all the endpoints of an API, as ':<|>'-separated values
130130-- of type 'AjaxReq'.
131−javascript :: HasForeign NoTypes () layout => Proxy layout -> Foreign () layout
131+javascript :: HasForeign NoTypes () api => Proxy api -> Foreign () api
132132javascript p = foreignFor (Proxy :: Proxy NoTypes) (Proxy :: Proxy ()) p defReq
133133
134134-- | Directly generate all the javascript functions for your API
servant-js/src/Servant/JS/Internal.hs+1−2
@@ -23,7 +23,6 @@ module Servant.JS.Internal
2323 , HasForeignType(..)
2424 , GenerateList(..)
2525 , NoTypes
26− , HeaderArg
2726 , ArgType(..)
2827 , HeaderArg(..)
2928 , QueryArg(..)
@@ -47,7 +46,7 @@ module Servant.JS.Internal
4746 , Header
4847 ) where
4948
50−import Control.Lens hiding (List)
49+import Control.Lens ((^.))
5150import qualified Data.CharSet as Set
5251import qualified Data.CharSet.Unicode.Category as Set
5352import Data.Monoid
servant-js/test/Servant/JSSpec/CustomHeaders.hs+12−12
@@ -23,11 +23,11 @@ import Servant.JS.Internal
2323-- using -- Basic, Digest, whatever.
2424data Authorization (sym :: Symbol) a
2525
26−instance (KnownSymbol sym, HasForeign lang () sublayout)
27− => HasForeign lang () (Authorization sym a :> sublayout) where
28− type Foreign () (Authorization sym a :> sublayout) = Foreign () sublayout
26+instance (KnownSymbol sym, HasForeign lang () api)
27+ => HasForeign lang () (Authorization sym a :> api) where
28+ type Foreign () (Authorization sym a :> api) = Foreign () api
2929
30− foreignFor lang ftype Proxy req = foreignFor lang ftype (Proxy :: Proxy sublayout) $
30+ foreignFor lang ftype Proxy req = foreignFor lang ftype (Proxy :: Proxy api) $
3131 req & reqHeaders <>~
3232 [ ReplaceHeaderArg (Arg "Authorization" ())
3333 $ tokenType (pack . symbolVal $ (Proxy :: Proxy sym)) ]
@@ -37,11 +37,11 @@ instance (KnownSymbol sym, HasForeign lang () sublayout)
3737-- | This is a combinator that fetches an X-MyLovelyHorse header.
3838data MyLovelyHorse a
3939
40−instance (HasForeign lang () sublayout)
41− => HasForeign lang () (MyLovelyHorse a :> sublayout) where
42− type Foreign () (MyLovelyHorse a :> sublayout) = Foreign () sublayout
40+instance (HasForeign lang () api)
41+ => HasForeign lang () (MyLovelyHorse a :> api) where
42+ type Foreign () (MyLovelyHorse a :> api) = Foreign () api
4343
44− foreignFor lang ftype Proxy req = foreignFor lang ftype (Proxy :: Proxy sublayout) $
44+ foreignFor lang ftype Proxy req = foreignFor lang ftype (Proxy :: Proxy api) $
4545 req & reqHeaders <>~ [ ReplaceHeaderArg (Arg "X-MyLovelyHorse" ()) tpl ]
4646 where
4747 tpl = "I am good friends with {X-MyLovelyHorse}"
@@ -49,11 +49,11 @@ instance (HasForeign lang () sublayout)
4949-- | This is a combinator that fetches an X-WhatsForDinner header.
5050data WhatsForDinner a
5151
52−instance (HasForeign lang () sublayout)
53− => HasForeign lang () (WhatsForDinner a :> sublayout) where
54− type Foreign () (WhatsForDinner a :> sublayout) = Foreign () sublayout
52+instance (HasForeign lang () api)
53+ => HasForeign lang () (WhatsForDinner a :> api) where
54+ type Foreign () (WhatsForDinner a :> api) = Foreign () api
5555
56− foreignFor lang ftype Proxy req = foreignFor lang ftype (Proxy :: Proxy sublayout) $
56+ foreignFor lang ftype Proxy req = foreignFor lang ftype (Proxy :: Proxy api) $
5757 req & reqHeaders <>~ [ ReplaceHeaderArg (Arg "X-WhatsForDinner" ()) tpl ]
5858 where
5959 tpl = "I would like {X-WhatsForDinner} with a cherry on top."
servant-lucid/LICENSEdeleted−30
@@ -1,30 +0,0 @@
1−Copyright (c) 2015-2016, Servant Contributors
2−
3−All rights reserved.
4−
5−Redistribution and use in source and binary forms, with or without
6−modification, are permitted provided that the following conditions are met:
7−
8− * Redistributions of source code must retain the above copyright
9− notice, this list of conditions and the following disclaimer.
10−
11− * Redistributions in binary form must reproduce the above
12− copyright notice, this list of conditions and the following
13− disclaimer in the documentation and/or other materials provided
14− with the distribution.
15−
16− * Neither the name of Julian K. Arni nor the names of other
17− contributors may be used to endorse or promote products derived
18− from this software without specific prior written permission.
19−
20−THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21−"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22−LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23−A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24−OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25−SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26−LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27−DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28−THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29−(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30−OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-lucid/Setup.hsdeleted−2
@@ -1,2 +0,0 @@
1−import Distribution.Simple
2−main = defaultMain
servant-lucid/include/overlapping-compat.hdeleted−8
@@ -1,8 +0,0 @@
1−#if __GLASGOW_HASKELL__ >= 710
2−#define OVERLAPPABLE_ {-# OVERLAPPABLE #-}
3−#define OVERLAPPING_ {-# OVERLAPPING #-}
4−#else
5−{-# LANGUAGE OverlappingInstances #-}
6−#define OVERLAPPABLE_
7−#define OVERLAPPING_
8−#endif
servant-lucid/servant-lucid.cabaldeleted−33
@@ -1,33 +0,0 @@
1−-- Initial servant-lucid.cabal generated by cabal init. For further
2−-- documentation, see http://haskell.org/cabal/users-guide/
3−
4−name: servant-lucid
5−version: 0.6
6−synopsis: Servant support for lucid
7−-- description:
8−homepage: http://haskell-servant.github.io/
9−license: BSD3
10−license-file: LICENSE
11−author: Servant Contributors
12−maintainer: haskell-servant-maintainers@googlegroups.com
13−copyright: 2015-2016 Servant Contributors
14−category: Web
15−build-type: Simple
16−extra-source-files: include/*.h
17−cabal-version: >=1.10
18−bug-reports: http://github.com/haskell-servant/servant/issues
19−source-repository head
20− type: git
21− location: http://github.com/haskell-servant/servant.git
22−
23−library
24− exposed-modules: Servant.HTML.Lucid
25− -- other-modules:
26− -- other-extensions:
27− build-depends: base >=4.7 && <5
28− , http-media
29− , lucid
30− , servant == 0.6.*
31− hs-source-dirs: src
32− default-language: Haskell2010
33− include-dirs: include
servant-lucid/src/Servant/HTML/Lucid.hsdeleted−36
@@ -1,36 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE MultiParamTypeClasses #-}
5−{-# LANGUAGE OverloadedStrings #-}
6−
7−#include "overlapping-compat.h"
8−
9−-- | An @HTML@ empty data type with `MimeRender` instances for @lucid@'s
10−-- `ToHtml` class and `Html` datatype.
11−-- You should only need to import this module for it's instances and the
12−-- `HTML` datatype.:
13−--
14−-- >>> type Eg = Get '[HTML] a
15−--
16−-- Will then check that @a@ has a `ToHtml` instance, or is `Html`.
17−module Servant.HTML.Lucid where
18−
19−import Data.Typeable (Typeable)
20−import Lucid (Html, ToHtml (..), renderBS)
21−import qualified Network.HTTP.Media as M
22−import Servant.API (Accept (..), MimeRender (..))
23−
24−data HTML deriving Typeable
25−
26−-- | @text/html;charset=utf-8@
27−instance Accept HTML where
28− contentType _ = "text" M.// "html" M./: ("charset", "utf-8")
29−
30−instance OVERLAPPABLE_
31− ToHtml a => MimeRender HTML a where
32− mimeRender _ = renderBS . toHtml
33−
34−instance OVERLAPPING_
35− MimeRender HTML (Html a) where
36− mimeRender _ = renderBS
servant-lucid/tinc.yamldeleted−3
@@ -1,3 +0,0 @@
1−dependencies:
2− - name: servant
3− path: ../servant
servant-mock/example/main.hs+3
@@ -2,6 +2,9 @@
22{-# LANGUAGE DeriveGeneric #-}
33{-# LANGUAGE GeneralizedNewtypeDeriving #-}
44{-# LANGUAGE TypeOperators #-}
5+
6+{-# OPTIONS_GHC -fno-warn-unused-binds #-}
7+
58import Data.Aeson
69import GHC.Generics
710import Network.Wai.Handler.Warp
servant-mock/servant-mock.cabal+11−6
@@ -1,5 +1,5 @@
11name: servant-mock
2−version: 0.6
2+version: 0.7.1
33synopsis: Derive a mock server for free from your servant API types
44description:
55 Derive a mock server for free from your servant API types
@@ -15,6 +15,10 @@ category: Web
1515build-type: Simple
1616extra-source-files: include/*.h
1717cabal-version: >=1.10
18+bug-reports: http://github.com/haskell-servant/servant/issues
19+source-repository head
20+ type: git
21+ location: http://github.com/haskell-servant/servant.git
1822
1923flag example
2024 description: Build the example too
@@ -27,14 +31,15 @@ library
2731 base >=4.7 && <5,
2832 bytestring >= 0.10 && <0.11,
2933 http-types >= 0.8 && <0.10,
30− servant >= 0.4,
31− servant-server >= 0.4,
32− transformers >= 0.3 && <0.5,
34+ servant == 0.7.*,
35+ servant-server == 0.7.*,
36+ transformers >= 0.3 && <0.6,
3337 QuickCheck >= 2.7 && <2.9,
3438 wai >= 3.0 && <3.3
3539 hs-source-dirs: src
3640 default-language: Haskell2010
3741 include-dirs: include
42+ ghc-options: -Wall
3843
3944executable mock-app
4045 main-is: main.hs
@@ -45,11 +50,11 @@ executable mock-app
4550 buildable: True
4651 else
4752 buildable: False
53+ ghc-options: -Wall
4854
4955test-suite spec
5056 type: exitcode-stdio-1.0
51− ghc-options:
52− -Wall -fno-warn-name-shadowing
57+ ghc-options: -Wall
5358 default-language: Haskell2010
5459 hs-source-dirs: test
5560 main-is: Spec.hs
servant-mock/src/Servant/Mock.hs+5−5
@@ -36,7 +36,7 @@
3636-- and call 'mock', which has the following type:
3737--
3838-- @
39−-- 'mock' :: 'HasMock' api => 'Proxy' api -> 'Server' api
39+-- 'mock' :: 'HasMock' api context => 'Proxy' api -> 'Proxy' context -> 'Server' api
4040-- @
4141--
4242-- What this says is, given some API type @api@ that it knows it can
@@ -52,7 +52,7 @@
5252-- @
5353-- main :: IO ()
5454-- main = Network.Wai.Handler.Warp.run 8080 $
55−-- 'serve' myAPI ('mock' myAPI)
55+-- 'serve' myAPI ('mock' myAPI Proxy)
5656-- @
5757module Servant.Mock ( HasMock(..) ) where
5858
@@ -90,15 +90,15 @@ class HasServer api context => HasMock api context where
9090 -- -- let's say we will start with the frontend,
9191 -- -- and hence need a placeholder server
9292 -- server :: Server API
93− -- server = mock api
93+ -- server = mock api Proxy
9494 -- @
9595 --
9696 -- What happens here is that @'Server' API@
9797 -- actually "means" 2 request handlers, of the following types:
9898 --
9999 -- @
100− -- getUser :: ExceptT ServantErr IO User
101− -- getBook :: ExceptT ServantErr IO Book
100+ -- getUser :: Handler User
101+ -- getBook :: Handler Book
102102 -- @
103103 --
104104 -- So under the hood, 'mock' uses the 'IO' bit to generate
servant-server/CHANGELOG.md+30
@@ -1,3 +1,33 @@
1+0.7.1
2+------
3+
4+* Remove module `Servant.Server.Internal.Enter` (https://github.com/haskell-servant/servant/pull/478)
5+* Support GHC 8.0
6+
7+0.7
8+---
9+
10+* The `Router` type has been changed. Static router tables should now
11+ be properly shared between requests, drastically increasing the
12+ number of situations where servers will be able to route requests
13+ efficiently. Functions `layout` and `layoutWithContext` have been
14+ added to visualize the router layout for debugging purposes. Test
15+ cases for expected router layouts have been added.
16+* If an endpoint is discovered to have a non-matching "accept header",
17+ this is now a recoverable rather than a fatal failure, allowing
18+ different endpoints for the same route, but with different content
19+ types to be specified modularly.
20+* Export `throwError` from module `Servant`
21+* Add `Handler` type synonym
22+
23+0.6.1
24+-----
25+
26+* If servers use the `BasicAuth` combinator and receive requests with missing or
27+ invalid credentials, the resulting error responses (401 and 403) could be
28+ overwritten by subsequent alternative routes. Now `BasicAuth` uses `FailFatal`
29+ and the error responses can't be overwritten anymore.
30+
1310.6
232---
333
servant-server/README.md+1−2
@@ -6,5 +6,4 @@ This library lets you *implement* an HTTP server with handlers for each endpoint
66
77## Getting started
88
9−We've written a [tutorial](http://haskell-servant.github.io/tutorial/) guide that introduces the core types and features of servant. After this article, you should be able to write your first servant webservices, learning the rest from the haddocks' examples.
10−
9+We've written a [tutorial](http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html) guide that introduces the core types and features of servant. After this article, you should be able to write your first servant webservices, learning the rest from the haddocks' examples.
servant-server/example/greet.hs+1−1
@@ -44,7 +44,7 @@ testApi = Proxy
4444-- There's one handler per endpoint, which, just like in the type
4545-- that represents the API, are glued together using :<|>.
4646--
47−-- Each handler runs in the 'ExceptT ServantErr IO' monad.
47+-- Each handler runs in the 'Handler' monad.
4848server :: Server TestApi
4949server = helloH :<|> postGreetH :<|> deleteGreetH
5050
servant-server/servant-server.cabal+15−14
@@ -1,17 +1,17 @@
11name: servant-server
2−version: 0.6
2+version: 0.7.1
33synopsis: A family of combinators for defining webservices APIs and serving them
44description:
55 A family of combinators for defining webservices APIs and serving them
66 .
7− You can learn about the basics in the <http://haskell-servant.github.io/tutorial tutorial>.
7+ You can learn about the basics in the <http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html tutorial>.
88 .
99 <https://github.com/haskell-servant/servant/blob/master/servant-server/example/greet.hs Here>
1010 is a runnable example, with comments, that defines a dummy API and implements
1111 a webserver that serves this API, using this package.
1212 .
1313 <https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md CHANGELOG>
14−homepage: http://haskell-servant.github.io/
14+homepage: http://haskell-servant.readthedocs.org/
1515Bug-reports: http://github.com/haskell-servant/servant/issues
1616license: BSD3
1717license-file: LICENSE
@@ -40,7 +40,6 @@ library
4040 Servant.Server.Internal
4141 Servant.Server.Internal.BasicAuth
4242 Servant.Server.Internal.Context
43− Servant.Server.Internal.Enter
4443 Servant.Server.Internal.Router
4544 Servant.Server.Internal.RoutingApplication
4645 Servant.Server.Internal.ServantErr
@@ -57,25 +56,26 @@ library
5756 , http-types >= 0.8 && < 0.10
5857 , network-uri >= 2.6 && < 2.7
5958 , mtl >= 2 && < 3
60− , mmorph >= 1
6159 , network >= 2.6 && < 2.7
6260 , safe >= 0.3 && < 0.4
63− , servant == 0.6.*
61+ , servant == 0.7.*
6462 , split >= 0.2 && < 0.3
6563 , string-conversions >= 0.3 && < 0.5
6664 , system-filepath >= 0.4 && < 0.5
6765 , filepath >= 1
6866 , text >= 1.2 && < 1.3
69− , transformers >= 0.3 && < 0.5
67+ , transformers >= 0.3 && < 0.6
7068 , transformers-compat>= 0.4
7169 , wai >= 3.0 && < 3.3
72− , wai-app-static >= 3.0 && < 3.2
70+ , wai-app-static >= 3.1 && < 3.2
7371 , warp >= 3.0 && < 3.3
7472 , word8 == 0.1.*
7573
7674 hs-source-dirs: src
7775 default-language: Haskell2010
7876 ghc-options: -Wall
77+ if impl(ghc >= 8.0)
78+ ghc-options: -Wno-redundant-constraints
7979 include-dirs: include
8080
8181executable greet
@@ -94,23 +94,24 @@ executable greet
9494
9595test-suite spec
9696 type: exitcode-stdio-1.0
97− ghc-options:
98− -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures
97+ ghc-options: -Wall
9998 default-language: Haskell2010
10099 hs-source-dirs: test
101100 main-is: Spec.hs
102101 other-modules:
103102 Servant.Server.ErrorSpec
104103 Servant.Server.Internal.ContextSpec
105− Servant.Server.Internal.EnterSpec
106− Servant.ServerSpec
104+ Servant.Server.RouterSpec
105+ Servant.Server.StreamingSpec
107106 Servant.Server.UsingContextSpec
108107 Servant.Server.UsingContextSpec.TestCombinators
108+ Servant.ServerSpec
109109 Servant.Utils.StaticFilesSpec
110110 build-depends:
111111 base == 4.*
112112 , base-compat
113113 , aeson
114+ , base64-bytestring
114115 , bytestring
115116 , bytestring-conversion
116117 , directory
@@ -125,7 +126,7 @@ test-suite spec
125126 , servant
126127 , servant-server
127128 , string-conversions
128− , should-not-typecheck == 2.*
129+ , should-not-typecheck == 2.1.*
129130 , temporary
130131 , text
131132 , transformers
@@ -146,5 +147,5 @@ test-suite doctests
146147 main-is: test/Doctests.hs
147148 buildable: True
148149 default-language: Haskell2010
149− ghc-options: -threaded
150+ ghc-options: -Wall -threaded
150151 include-dirs: include
servant-server/src/Servant.hs+2
@@ -10,8 +10,10 @@ module Servant (
1010 module Servant.Utils.StaticFiles,
1111 -- | Useful re-exports
1212 Proxy(..),
13+ throwError
1314 ) where
1415
16+import Control.Monad.Error.Class (throwError)
1517import Data.Proxy
1618import Servant.API
1719import Servant.Server
servant-server/src/Servant/Server.hs+74−8
@@ -17,6 +17,11 @@ module Servant.Server
1717 , -- * Handlers for all standard combinators
1818 HasServer(..)
1919 , Server
20+ , Handler
21+
22+ -- * Debugging the server layout
23+ , layout
24+ , layoutWithContext
2025
2126 -- * Enter
2227 -- $enterDoc
@@ -90,12 +95,16 @@ module Servant.Server
9095 , err504
9196 , err505
9297
98+ -- * Re-exports
99+ , Application
100+
93101 ) where
94102
95103import Data.Proxy (Proxy)
104+import Data.Text (Text)
96105import Network.Wai (Application)
97106import Servant.Server.Internal
98−import Servant.Server.Internal.Enter
107+import Servant.Utils.Enter
99108
100109
101110-- * Implementing Servers
@@ -121,16 +130,73 @@ import Servant.Server.Internal.Enter
121130-- > main :: IO ()
122131-- > main = Network.Wai.Handler.Warp.run 8080 app
123132--
124−serve :: (HasServer layout '[]) => Proxy layout -> Server layout -> Application
133+serve :: (HasServer api '[]) => Proxy api -> Server api -> Application
125134serve p = serveWithContext p EmptyContext
126135
127−serveWithContext :: (HasServer layout context)
128− => Proxy layout -> Context context -> Server layout -> Application
129−serveWithContext p context server = toApplication (runRouter (route p context d))
130− where
131− d = Delayed r r r r (\ _ _ _ -> Route server)
132− r = return (Route ())
136+serveWithContext :: (HasServer api context)
137+ => Proxy api -> Context context -> Server api -> Application
138+serveWithContext p context server =
139+ toApplication (runRouter (route p context (emptyDelayed (Route server))))
133140
141+-- | The function 'layout' produces a textual description of the internal
142+-- router layout for debugging purposes. Note that the router layout is
143+-- determined just by the API, not by the handlers.
144+--
145+-- Example:
146+--
147+-- For the following API
148+--
149+-- > type API =
150+-- > "a" :> "d" :> Get '[JSON] ()
151+-- > :<|> "b" :> Capture "x" Int :> Get '[JSON] Bool
152+-- > :<|> "c" :> Put '[JSON] Bool
153+-- > :<|> "a" :> "e" :> Get '[JSON] Int
154+-- > :<|> "b" :> Capture "x" Int :> Put '[JSON] Bool
155+-- > :<|> Raw
156+--
157+-- we get the following output:
158+--
159+-- > /
160+-- > ├─ a/
161+-- > │ ├─ d/
162+-- > │ │ └─•
163+-- > │ └─ e/
164+-- > │ └─•
165+-- > ├─ b/
166+-- > │ └─ <capture>/
167+-- > │ ├─•
168+-- > │ ┆
169+-- > │ └─•
170+-- > ├─ c/
171+-- > │ └─•
172+-- > ┆
173+-- > └─ <raw>
174+--
175+-- Explanation of symbols:
176+--
177+-- [@├@] Normal lines reflect static branching via a table.
178+--
179+-- [@a/@] Nodes reflect static path components.
180+--
181+-- [@─•@] Leaves reflect endpoints.
182+--
183+-- [@\<capture\>/@] This is a delayed capture of a path component.
184+--
185+-- [@\<raw\>@] This is a part of the API we do not know anything about.
186+--
187+-- [@┆@] Dashed lines suggest a dynamic choice between the part above
188+-- and below. If there is a success for fatal failure in the first part,
189+-- that one takes precedence. If both parts fail, the \"better\" error
190+-- code will be returned.
191+--
192+layout :: (HasServer api '[]) => Proxy api -> Text
193+layout p = layoutWithContext p EmptyContext
194+
195+-- | Variant of 'layout' that takes an additional 'Context'.
196+layoutWithContext :: (HasServer api context)
197+ => Proxy api -> Context context -> Text
198+layoutWithContext p context =
199+ routerLayout (route p context (emptyDelayed (FailFatal err501)))
134200
135201-- Documentation
136202
servant-server/src/Servant/Server/Experimental/Auth.hs+14−12
@@ -12,8 +12,8 @@
1212
1313module Servant.Server.Experimental.Auth where
1414
15−import Control.Monad.Trans.Except (ExceptT,
16− runExceptT)
15+import Control.Monad.Trans (liftIO)
16+import Control.Monad.Trans.Except (runExceptT)
1717import Data.Proxy (Proxy (Proxy))
1818import Data.Typeable (Typeable)
1919import GHC.Generics (Generic)
@@ -25,10 +25,11 @@ import Servant.Server.Internal (HasContextEntry,
2525 HasServer, ServerT,
2626 getContextEntry,
2727 route)
28−import Servant.Server.Internal.Router (Router' (WithRequest))
29−import Servant.Server.Internal.RoutingApplication (RouteResult (FailFatal, Route),
30− addAuthCheck)
31−import Servant.Server.Internal.ServantErr (ServantErr)
28+import Servant.Server.Internal.RoutingApplication (addAuthCheck,
29+ delayedFailFatal,
30+ DelayedIO,
31+ withRequest)
32+import Servant.Server.Internal.ServantErr (Handler)
3233
3334-- * General Auth
3435
@@ -42,11 +43,11 @@ type family AuthServerData a :: *
4243--
4344-- NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
4445newtype AuthHandler r usr = AuthHandler
45− { unAuthHandler :: r -> ExceptT ServantErr IO usr }
46+ { unAuthHandler :: r -> Handler usr }
4647 deriving (Generic, Typeable)
4748
4849-- | NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
49−mkAuthHandler :: (r -> ExceptT ServantErr IO usr) -> AuthHandler r usr
50+mkAuthHandler :: (r -> Handler usr) -> AuthHandler r usr
5051mkAuthHandler = AuthHandler
5152
5253-- | Known orphan instance.
@@ -58,9 +59,10 @@ instance ( HasServer api context
5859 type ServerT (AuthProtect tag :> api) m =
5960 AuthServerData (AuthProtect tag) -> ServerT api m
6061
61− route Proxy context subserver = WithRequest $ \ request ->
62− route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck request)
62+ route Proxy context subserver =
63+ route (Proxy :: Proxy api) context (subserver `addAuthCheck` withRequest authCheck)
6364 where
65+ authHandler :: Request -> Handler (AuthServerData (AuthProtect tag))
6466 authHandler = unAuthHandler (getContextEntry context)
65− authCheck = fmap (either FailFatal Route) . runExceptT . authHandler
66−
67+ authCheck :: Request -> DelayedIO (AuthServerData (AuthProtect tag))
68+ authCheck = (>>= either delayedFailFatal return) . liftIO . runExceptT . authHandler
servant-server/src/Servant/Server/Internal.hs+114−114
@@ -22,15 +22,13 @@ module Servant.Server.Internal
2222 , module Servant.Server.Internal.ServantErr
2323 ) where
2424
25−import Control.Monad.Trans.Except (ExceptT)
25+import Control.Monad.Trans (liftIO)
2626import qualified Data.ByteString as B
2727import qualified Data.ByteString.Char8 as BC8
2828import qualified Data.ByteString.Lazy as BL
29−import qualified Data.Map as M
3029import Data.Maybe (fromMaybe, mapMaybe)
3130import Data.String (fromString)
3231import Data.String.Conversions (cs, (<>))
33−import Data.Text (Text)
3432import Data.Typeable
3533import GHC.TypeLits (KnownNat, KnownSymbol, natVal,
3634 symbolVal)
@@ -38,7 +36,7 @@ import Network.HTTP.Types hiding (Header, ResponseHeaders)
3836import Network.Socket (SockAddr)
3937import Network.Wai (Application, Request, Response,
4038 httpVersion, isSecure,
41− lazyRequestBody, pathInfo,
39+ lazyRequestBody,
4240 rawQueryString, remoteHost,
4341 requestHeaders, requestMethod,
4442 responseLBS, vault)
@@ -70,12 +68,16 @@ import Servant.Server.Internal.RoutingApplication
7068import Servant.Server.Internal.ServantErr
7169
7270
73−class HasServer layout context where
74− type ServerT layout (m :: * -> *) :: *
71+class HasServer api context where
72+ type ServerT api (m :: * -> *) :: *
7573
76− route :: Proxy layout -> Context context -> Delayed (Server layout) -> Router
74+ route ::
75+ Proxy api
76+ -> Context context
77+ -> Delayed env (Server api)
78+ -> Router env
7779
78−type Server layout = ServerT layout (ExceptT ServantErr IO)
80+type Server api = ServerT api Handler
7981
8082-- * Instances
8183
@@ -95,7 +97,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
9597 type ServerT (a :<|> b) m = ServerT a m :<|> ServerT b m
9698
9799 route Proxy context server = choice (route pa context ((\ (a :<|> _) -> a) <$> server))
98− (route pb context ((\ (_ :<|> b) -> b) <$> server))
100+ (route pb context ((\ (_ :<|> b) -> b) <$> server))
99101 where pa = Proxy :: Proxy a
100102 pb = Proxy :: Proxy b
101103
@@ -114,21 +116,21 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
114116-- >
115117-- > server :: Server MyApi
116118-- > server = getBook
117−-- > where getBook :: Text -> ExceptT ServantErr IO Book
119+-- > where getBook :: Text -> Handler Book
118120-- > getBook isbn = ...
119−instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout context)
120− => HasServer (Capture capture a :> sublayout) context where
121+instance (KnownSymbol capture, FromHttpApiData a, HasServer api context)
122+ => HasServer (Capture capture a :> api) context where
121123
122− type ServerT (Capture capture a :> sublayout) m =
123− a -> ServerT sublayout m
124+ type ServerT (Capture capture a :> api) m =
125+ a -> ServerT api m
124126
125127 route Proxy context d =
126− DynamicRouter $ \ first ->
127− route (Proxy :: Proxy sublayout)
128+ CaptureRouter $
129+ route (Proxy :: Proxy api)
128130 context
129− (addCapture d $ case parseUrlPieceMaybe first :: Maybe a of
130− Nothing -> return $ Fail err400
131− Just v -> return $ Route v
131+ (addCapture d $ \ txt -> case parseUrlPieceMaybe txt :: Maybe a of
132+ Nothing -> delayedFail err400
133+ Just v -> return v
132134 )
133135
134136allowedMethodHead :: Method -> Request -> Bool
@@ -147,48 +149,51 @@ processMethodRouter handleA status method headers request = case handleA of
147149 bdy = if allowedMethodHead method request then "" else body
148150 hdrs = (hContentType, cs contentT) : (fromMaybe [] headers)
149151
150−methodCheck :: Method -> Request -> IO (RouteResult ())
152+methodCheck :: Method -> Request -> DelayedIO ()
151153methodCheck method request
152− | allowedMethod method request = return $ Route ()
153− | otherwise = return $ Fail err405
154−
155−acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> IO (RouteResult ())
154+ | allowedMethod method request = return ()
155+ | otherwise = delayedFail err405
156+
157+-- This has switched between using 'Fail' and 'FailFatal' a number of
158+-- times. If the 'acceptCheck' is run after the body check (which would
159+-- be morally right), then we have to set this to 'FailFatal', because
160+-- the body check is not reversible, and therefore backtracking after the
161+-- body check is no longer an option. However, we now run the accept
162+-- check before the body check and can therefore afford to make it
163+-- recoverable.
164+acceptCheck :: (AllMime list) => Proxy list -> B.ByteString -> DelayedIO ()
156165acceptCheck proxy accH
157− | canHandleAcceptH proxy (AcceptHeader accH) = return $ Route ()
158− | otherwise = return $ FailFatal err406
166+ | canHandleAcceptH proxy (AcceptHeader accH) = return ()
167+ | otherwise = delayedFail err406
159168
160169methodRouter :: (AllCTRender ctypes a)
161170 => Method -> Proxy ctypes -> Status
162− -> Delayed (ExceptT ServantErr IO a)
163− -> Router
164−methodRouter method proxy status action = LeafRouter route'
171+ -> Delayed env (Handler a)
172+ -> Router env
173+methodRouter method proxy status action = leafRouter route'
165174 where
166− route' request respond
167− | pathIsEmpty request =
175+ route' env request respond =
168176 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
169177 in runAction (action `addMethodCheck` methodCheck method request
170178 `addAcceptCheck` acceptCheck proxy accH
171− ) respond $ \ output -> do
179+ ) env request respond $ \ output -> do
172180 let handleA = handleAcceptH proxy (AcceptHeader accH) output
173181 processMethodRouter handleA status method Nothing request
174− | otherwise = respond $ Fail err404
175182
176183methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
177184 => Method -> Proxy ctypes -> Status
178− -> Delayed (ExceptT ServantErr IO (Headers h v))
179− -> Router
180−methodRouterHeaders method proxy status action = LeafRouter route'
185+ -> Delayed env (Handler (Headers h v))
186+ -> Router env
187+methodRouterHeaders method proxy status action = leafRouter route'
181188 where
182− route' request respond
183− | pathIsEmpty request =
189+ route' env request respond =
184190 let accH = fromMaybe ct_wildcard $ lookup hAccept $ requestHeaders request
185191 in runAction (action `addMethodCheck` methodCheck method request
186192 `addAcceptCheck` acceptCheck proxy accH
187− ) respond $ \ output -> do
193+ ) env request respond $ \ output -> do
188194 let headers = getHeaders output
189195 handleA = handleAcceptH proxy (AcceptHeader accH) (getResponse output)
190196 processMethodRouter handleA status method (Just headers) request
191− | otherwise = respond $ Fail err404
192197
193198instance OVERLAPPABLE_
194199 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
@@ -229,17 +234,17 @@ instance OVERLAPPING_
229234-- >
230235-- > server :: Server MyApi
231236-- > server = viewReferer
232−-- > where viewReferer :: Referer -> ExceptT ServantErr IO referer
237+-- > where viewReferer :: Referer -> Handler referer
233238-- > viewReferer referer = return referer
234−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
235− => HasServer (Header sym a :> sublayout) context where
239+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
240+ => HasServer (Header sym a :> api) context where
236241
237− type ServerT (Header sym a :> sublayout) m =
238− Maybe a -> ServerT sublayout m
242+ type ServerT (Header sym a :> api) m =
243+ Maybe a -> ServerT api m
239244
240− route Proxy context subserver = WithRequest $ \ request ->
241− let mheader = parseHeaderMaybe =<< lookup str (requestHeaders request)
242− in route (Proxy :: Proxy sublayout) context (passToServer subserver mheader)
245+ route Proxy context subserver =
246+ let mheader req = parseHeaderMaybe =<< lookup str (requestHeaders req)
247+ in route (Proxy :: Proxy api) context (passToServer subserver mheader)
243248 where str = fromString $ symbolVal (Proxy :: Proxy sym)
244249
245250-- | If you use @'QueryParam' "author" Text@ in one of the endpoints for your API,
@@ -260,24 +265,24 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
260265-- >
261266-- > server :: Server MyApi
262267-- > server = getBooksBy
263−-- > where getBooksBy :: Maybe Text -> ExceptT ServantErr IO [Book]
268+-- > where getBooksBy :: Maybe Text -> Handler [Book]
264269-- > getBooksBy Nothing = ...return all books...
265270-- > getBooksBy (Just author) = ...return books by the given author...
266−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
267− => HasServer (QueryParam sym a :> sublayout) context where
271+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
272+ => HasServer (QueryParam sym a :> api) context where
268273
269− type ServerT (QueryParam sym a :> sublayout) m =
270− Maybe a -> ServerT sublayout m
274+ type ServerT (QueryParam sym a :> api) m =
275+ Maybe a -> ServerT api m
271276
272− route Proxy context subserver = WithRequest $ \ request ->
273− let querytext = parseQueryText $ rawQueryString request
274− param =
275− case lookup paramname querytext of
277+ route Proxy context subserver =
278+ let querytext r = parseQueryText $ rawQueryString r
279+ param r =
280+ case lookup paramname (querytext r) of
276281 Nothing -> Nothing -- param absent from the query string
277282 Just Nothing -> Nothing -- param present with no value -> Nothing
278283 Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
279284 -- the right type
280− in route (Proxy :: Proxy sublayout) context (passToServer subserver param)
285+ in route (Proxy :: Proxy api) context (passToServer subserver param)
281286 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
282287
283288-- | If you use @'QueryParams' "authors" Text@ in one of the endpoints for your API,
@@ -297,22 +302,22 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
297302-- >
298303-- > server :: Server MyApi
299304-- > server = getBooksBy
300−-- > where getBooksBy :: [Text] -> ExceptT ServantErr IO [Book]
305+-- > where getBooksBy :: [Text] -> Handler [Book]
301306-- > getBooksBy authors = ...return all books by these authors...
302−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
303− => HasServer (QueryParams sym a :> sublayout) context where
307+instance (KnownSymbol sym, FromHttpApiData a, HasServer api context)
308+ => HasServer (QueryParams sym a :> api) context where
304309
305− type ServerT (QueryParams sym a :> sublayout) m =
306− [a] -> ServerT sublayout m
310+ type ServerT (QueryParams sym a :> api) m =
311+ [a] -> ServerT api m
307312
308− route Proxy context subserver = WithRequest $ \ request ->
309− let querytext = parseQueryText $ rawQueryString request
313+ route Proxy context subserver =
314+ let querytext r = parseQueryText $ rawQueryString r
310315 -- if sym is "foo", we look for query string parameters
311316 -- named "foo" or "foo[]" and call parseQueryParam on the
312317 -- corresponding values
313− parameters = filter looksLikeParam querytext
314− values = mapMaybe (convert . snd) parameters
315− in route (Proxy :: Proxy sublayout) context (passToServer subserver values)
318+ parameters r = filter looksLikeParam (querytext r)
319+ values r = mapMaybe (convert . snd) (parameters r)
320+ in route (Proxy :: Proxy api) context (passToServer subserver values)
316321 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
317322 looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
318323 convert Nothing = Nothing
@@ -328,21 +333,21 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
328333-- >
329334-- > server :: Server MyApi
330335-- > server = getBooks
331−-- > where getBooks :: Bool -> ExceptT ServantErr IO [Book]
336+-- > where getBooks :: Bool -> Handler [Book]
332337-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
333−instance (KnownSymbol sym, HasServer sublayout context)
334− => HasServer (QueryFlag sym :> sublayout) context where
338+instance (KnownSymbol sym, HasServer api context)
339+ => HasServer (QueryFlag sym :> api) context where
335340
336− type ServerT (QueryFlag sym :> sublayout) m =
337− Bool -> ServerT sublayout m
341+ type ServerT (QueryFlag sym :> api) m =
342+ Bool -> ServerT api m
338343
339− route Proxy context subserver = WithRequest $ \ request ->
340− let querytext = parseQueryText $ rawQueryString request
341− param = case lookup paramname querytext of
344+ route Proxy context subserver =
345+ let querytext r = parseQueryText $ rawQueryString r
346+ param r = case lookup paramname (querytext r) of
342347 Just Nothing -> True -- param is there, with no value
343348 Just (Just v) -> examine v -- param with a value
344349 Nothing -> False -- param not in the query string
345− in route (Proxy :: Proxy sublayout) context (passToServer subserver param)
350+ in route (Proxy :: Proxy api) context (passToServer subserver param)
346351 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
347352 examine v | v == "true" || v == "1" || v == "" = True
348353 | otherwise = False
@@ -359,8 +364,8 @@ instance HasServer Raw context where
359364
360365 type ServerT Raw m = Application
361366
362− route Proxy _ rawApplication = LeafRouter $ \ request respond -> do
363− r <- runDelayed rawApplication
367+ route Proxy _ rawApplication = RawRouter $ \ env request respond -> do
368+ r <- runDelayed rawApplication env request
364369 case r of
365370 Route app -> app request (respond . Route)
366371 Fail a -> respond $ Fail a
@@ -385,18 +390,18 @@ instance HasServer Raw context where
385390-- >
386391-- > server :: Server MyApi
387392-- > server = postBook
388−-- > where postBook :: Book -> ExceptT ServantErr IO Book
393+-- > where postBook :: Book -> Handler Book
389394-- > postBook book = ...insert into your db...
390−instance ( AllCTUnrender list a, HasServer sublayout context
391− ) => HasServer (ReqBody list a :> sublayout) context where
395+instance ( AllCTUnrender list a, HasServer api context
396+ ) => HasServer (ReqBody list a :> api) context where
392397
393− type ServerT (ReqBody list a :> sublayout) m =
394− a -> ServerT sublayout m
398+ type ServerT (ReqBody list a :> api) m =
399+ a -> ServerT api m
395400
396− route Proxy context subserver = WithRequest $ \ request ->
397− route (Proxy :: Proxy sublayout) context (addBodyCheck subserver (bodyCheck request))
401+ route Proxy context subserver =
402+ route (Proxy :: Proxy api) context (addBodyCheck subserver bodyCheck)
398403 where
399− bodyCheck request = do
404+ bodyCheck = withRequest $ \ request -> do
400405 -- See HTTP RFC 2616, section 7.2.1
401406 -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
402407 -- See also "W3C Internet Media Type registration, consistency of use"
@@ -404,48 +409,49 @@ instance ( AllCTUnrender list a, HasServer sublayout context
404409 let contentTypeH = fromMaybe "application/octet-stream"
405410 $ lookup hContentType $ requestHeaders request
406411 mrqbody <- handleCTypeH (Proxy :: Proxy list) (cs contentTypeH)
407− <$> lazyRequestBody request
412+ <$> liftIO (lazyRequestBody request)
408413 case mrqbody of
409− Nothing -> return $ FailFatal err415
410− Just (Left e) -> return $ FailFatal err400 { errBody = cs e }
411− Just (Right v) -> return $ Route v
414+ Nothing -> delayedFailFatal err415
415+ Just (Left e) -> delayedFailFatal err400 { errBody = cs e }
416+ Just (Right v) -> return v
412417
413418-- | Make sure the incoming request starts with @"/path"@, strip it and
414−-- pass the rest of the request path to @sublayout@.
415−instance (KnownSymbol path, HasServer sublayout context) => HasServer (path :> sublayout) context where
419+-- pass the rest of the request path to @api@.
420+instance (KnownSymbol path, HasServer api context) => HasServer (path :> api) context where
416421
417− type ServerT (path :> sublayout) m = ServerT sublayout m
422+ type ServerT (path :> api) m = ServerT api m
418423
419− route Proxy context subserver = StaticRouter $
420− M.singleton (cs (symbolVal proxyPath))
421− (route (Proxy :: Proxy sublayout) context subserver)
424+ route Proxy context subserver =
425+ pathRouter
426+ (cs (symbolVal proxyPath))
427+ (route (Proxy :: Proxy api) context subserver)
422428 where proxyPath = Proxy :: Proxy path
423429
424430instance HasServer api context => HasServer (RemoteHost :> api) context where
425431 type ServerT (RemoteHost :> api) m = SockAddr -> ServerT api m
426432
427− route Proxy context subserver = WithRequest $ \req ->
428− route (Proxy :: Proxy api) context (passToServer subserver $ remoteHost req)
433+ route Proxy context subserver =
434+ route (Proxy :: Proxy api) context (passToServer subserver remoteHost)
429435
430436instance HasServer api context => HasServer (IsSecure :> api) context where
431437 type ServerT (IsSecure :> api) m = IsSecure -> ServerT api m
432438
433− route Proxy context subserver = WithRequest $ \req ->
434− route (Proxy :: Proxy api) context (passToServer subserver $ secure req)
439+ route Proxy context subserver =
440+ route (Proxy :: Proxy api) context (passToServer subserver secure)
435441
436442 where secure req = if isSecure req then Secure else NotSecure
437443
438444instance HasServer api context => HasServer (Vault :> api) context where
439445 type ServerT (Vault :> api) m = Vault -> ServerT api m
440446
441− route Proxy context subserver = WithRequest $ \req ->
442− route (Proxy :: Proxy api) context (passToServer subserver $ vault req)
447+ route Proxy context subserver =
448+ route (Proxy :: Proxy api) context (passToServer subserver vault)
443449
444450instance HasServer api context => HasServer (HttpVersion :> api) context where
445451 type ServerT (HttpVersion :> api) m = HttpVersion -> ServerT api m
446452
447− route Proxy context subserver = WithRequest $ \req ->
448− route (Proxy :: Proxy api) context (passToServer subserver $ httpVersion req)
453+ route Proxy context subserver =
454+ route (Proxy :: Proxy api) context (passToServer subserver httpVersion)
449455
450456-- | Basic Authentication
451457instance ( KnownSymbol realm
@@ -456,21 +462,15 @@ instance ( KnownSymbol realm
456462
457463 type ServerT (BasicAuth realm usr :> api) m = usr -> ServerT api m
458464
459− route Proxy context subserver = WithRequest $ \ request ->
460− route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck request)
465+ route Proxy context subserver =
466+ route (Proxy :: Proxy api) context (subserver `addAuthCheck` authCheck)
461467 where
462468 realm = BC8.pack $ symbolVal (Proxy :: Proxy realm)
463469 basicAuthContext = getContextEntry context
464− authCheck req = runBasicAuth req realm basicAuthContext
470+ authCheck = withRequest $ \ req -> runBasicAuth req realm basicAuthContext
465471
466472-- * helpers
467473
468−pathIsEmpty :: Request -> Bool
469−pathIsEmpty = go . pathInfo
470− where go [] = True
471− go [""] = True
472− go _ = False
473−
474474ct_wildcard :: B.ByteString
475475ct_wildcard = "*" <> "/" <> "*" -- Because CPP
476476
servant-server/src/Servant/Server/Internal/BasicAuth.hs+12−11
@@ -1,11 +1,12 @@
1−{-# LANGUAGE DeriveFunctor #-}
2−{-# LANGUAGE DeriveGeneric #-}
31{-# LANGUAGE DeriveDataTypeable #-}
4−{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE DeriveFunctor #-}
3+{-# LANGUAGE DeriveGeneric #-}
4+{-# LANGUAGE OverloadedStrings #-}
55
66module Servant.Server.Internal.BasicAuth where
77
88import Control.Monad (guard)
9+import Control.Monad.Trans (liftIO)
910import qualified Data.ByteString as BS
1011import Data.ByteString.Base64 (decodeLenient)
1112import Data.Monoid ((<>))
@@ -15,9 +16,9 @@ import GHC.Generics
1516import Network.HTTP.Types (Header)
1617import Network.Wai (Request, requestHeaders)
1718
18−import Servant.API.BasicAuth (BasicAuthData(BasicAuthData))
19−import Servant.Server.Internal.RoutingApplication
20−import Servant.Server.Internal.ServantErr
19+import Servant.API.BasicAuth (BasicAuthData(BasicAuthData))
20+import Servant.Server.Internal.RoutingApplication
21+import Servant.Server.Internal.ServantErr
2122
2223-- * Basic Auth
2324
@@ -57,13 +58,13 @@ decodeBAHdr req = do
5758
5859-- | Run and check basic authentication, returning the appropriate http error per
5960-- the spec.
60−runBasicAuth :: Request -> BS.ByteString -> BasicAuthCheck usr -> IO (RouteResult usr)
61+runBasicAuth :: Request -> BS.ByteString -> BasicAuthCheck usr -> DelayedIO usr
6162runBasicAuth req realm (BasicAuthCheck ba) =
6263 case decodeBAHdr req of
6364 Nothing -> plzAuthenticate
64− Just e -> ba e >>= \res -> case res of
65+ Just e -> liftIO (ba e) >>= \res -> case res of
6566 BadPassword -> plzAuthenticate
6667 NoSuchUser -> plzAuthenticate
67− Unauthorized -> return $ Fail err403
68− Authorized usr -> return $ Route usr
69− where plzAuthenticate = return $ Fail err401 { errHeaders = [mkBAChallengerHdr realm] }
68+ Unauthorized -> delayedFailFatal err403
69+ Authorized usr -> return usr
70+ where plzAuthenticate = delayedFailFatal err401 { errHeaders = [mkBAChallengerHdr realm] }
servant-server/src/Servant/Server/Internal/Context.hs+2−2
@@ -18,7 +18,7 @@ import GHC.TypeLits
1818-- | 'Context's are used to pass values to combinators. (They are __not__ meant
1919-- to be used to pass parameters to your handlers, i.e. they should not replace
2020-- any custom 'Control.Monad.Trans.Reader.ReaderT'-monad-stack that you're using
21−-- with 'Servant.Server.Internal.Enter.enter'.) If you don't use combinators that
21+-- with 'Servant.Utils.Enter'.) If you don't use combinators that
2222-- require any context entries, you can just use 'Servant.Server.serve' as always.
2323--
2424-- If you are using combinators that require a non-empty 'Context' you have to
@@ -59,7 +59,7 @@ instance (Eq a, Eq (Context as)) => Eq (Context (a ': as)) where
5959--
6060-- >>> getContextEntry (True :. False :. EmptyContext) :: String
6161-- ...
62−-- No instance for (HasContextEntry '[] [Char])
62+-- ...No instance for (HasContextEntry '[] [Char])
6363-- ...
6464class HasContextEntry (context :: [*]) (val :: *) where
6565 getContextEntry :: Context context -> val
servant-server/src/Servant/Server/Internal/Router.hs+168−61
@@ -1,89 +1,196 @@
11{-# LANGUAGE DeriveFunctor #-}
22{-# LANGUAGE CPP #-}
3+{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE KindSignatures #-}
5+{-# LANGUAGE OverloadedStrings #-}
36module Servant.Server.Internal.Router where
47
58import Data.Map (Map)
69import qualified Data.Map as M
10+import Data.Monoid
711import Data.Text (Text)
8−import Network.Wai (Request, Response, pathInfo)
12+import qualified Data.Text as T
13+import Network.Wai (Response, pathInfo)
914import Servant.Server.Internal.RoutingApplication
1015import Servant.Server.Internal.ServantErr
1116
12−type Router = Router' RoutingApplication
17+type Router env = Router' env RoutingApplication
1318
1419-- | Internal representation of a router.
15−data Router' a =
16− WithRequest (Request -> Router)
17− -- ^ current request is passed to the router
18− | StaticRouter (Map Text Router)
19− -- ^ first path component used for lookup and removed afterwards
20− | DynamicRouter (Text -> Router)
21− -- ^ first path component used for lookup and removed afterwards
22− | LeafRouter a
23− -- ^ to be used for routes that match an empty path
24− | Choice Router Router
20+--
21+-- The first argument describes an environment type that is
22+-- expected as extra input by the routers at the leaves. The
23+-- environment is filled while running the router, with path
24+-- components that can be used to process captures.
25+--
26+data Router' env a =
27+ StaticRouter (Map Text (Router' env a)) [env -> a]
28+ -- ^ the map contains routers for subpaths (first path component used
29+ -- for lookup and removed afterwards), the list contains handlers
30+ -- for the empty path, to be tried in order
31+ | CaptureRouter (Router' (Text, env) a)
32+ -- ^ first path component is passed to the child router in its
33+ -- environment and removed afterwards
34+ | RawRouter (env -> a)
35+ -- ^ to be used for routes we do not know anything about
36+ | Choice (Router' env a) (Router' env a)
2537 -- ^ left-biased choice between two routers
2638 deriving Functor
2739
28−-- | Apply a transformation to the response of a `Router`.
29−tweakResponse :: (RouteResult Response -> RouteResult Response) -> Router -> Router
30−tweakResponse f = fmap (\a -> \req cont -> a req (cont . f))
40+-- | Smart constructor for a single static path component.
41+pathRouter :: Text -> Router' env a -> Router' env a
42+pathRouter t r = StaticRouter (M.singleton t r) []
43+
44+-- | Smart constructor for a leaf, i.e., a router that expects
45+-- the empty path.
46+--
47+leafRouter :: (env -> a) -> Router' env a
48+leafRouter l = StaticRouter M.empty [l]
3149
3250-- | Smart constructor for the choice between routers.
3351-- We currently optimize the following cases:
3452--
35−-- * Two static routers can be joined by joining their maps.
53+-- * Two static routers can be joined by joining their maps
54+-- and concatenating their leaf-lists.
3655-- * Two dynamic routers can be joined by joining their codomains.
37−-- * Two 'WithRequest' routers can be joined by passing them
38−-- the same request and joining their codomains.
39−-- * A 'WithRequest' router can be joined with anything else by
40−-- passing the same request to both but ignoring it in the
41−-- component that does not need it.
56+-- * Choice nodes can be reordered.
4257--
43−choice :: Router -> Router -> Router
44−choice (StaticRouter table1) (StaticRouter table2) =
45− StaticRouter (M.unionWith choice table1 table2)
46−choice (DynamicRouter fun1) (DynamicRouter fun2) =
47− DynamicRouter (\ first -> choice (fun1 first) (fun2 first))
48−choice (WithRequest router1) (WithRequest router2) =
49− WithRequest (\ request -> choice (router1 request) (router2 request))
50−choice (WithRequest router1) router2 =
51− WithRequest (\ request -> choice (router1 request) router2)
52−choice router1 (WithRequest router2) =
53− WithRequest (\ request -> choice router1 (router2 request))
58+choice :: Router' env a -> Router' env a -> Router' env a
59+choice (StaticRouter table1 ls1) (StaticRouter table2 ls2) =
60+ StaticRouter (M.unionWith choice table1 table2) (ls1 ++ ls2)
61+choice (CaptureRouter router1) (CaptureRouter router2) =
62+ CaptureRouter (choice router1 router2)
63+choice router1 (Choice router2 router3) = Choice (choice router1 router2) router3
5464choice router1 router2 = Choice router1 router2
5565
66+-- | Datatype used for representing and debugging the
67+-- structure of a router. Abstracts from the handlers
68+-- at the leaves.
69+--
70+-- Two 'Router's can be structurally compared by computing
71+-- their 'RouterStructure' using 'routerStructure' and
72+-- then testing for equality, see 'sameStructure'.
73+--
74+data RouterStructure =
75+ StaticRouterStructure (Map Text RouterStructure) Int
76+ | CaptureRouterStructure RouterStructure
77+ | RawRouterStructure
78+ | ChoiceStructure RouterStructure RouterStructure
79+ deriving (Eq, Show)
80+
81+-- | Compute the structure of a router.
82+--
83+-- Assumes that the request or text being passed
84+-- in 'WithRequest' or 'CaptureRouter' does not
85+-- affect the structure of the underlying tree.
86+--
87+routerStructure :: Router' env a -> RouterStructure
88+routerStructure (StaticRouter m ls) =
89+ StaticRouterStructure (fmap routerStructure m) (length ls)
90+routerStructure (CaptureRouter router) =
91+ CaptureRouterStructure $
92+ routerStructure router
93+routerStructure (RawRouter _) =
94+ RawRouterStructure
95+routerStructure (Choice r1 r2) =
96+ ChoiceStructure
97+ (routerStructure r1)
98+ (routerStructure r2)
99+
100+-- | Compare the structure of two routers.
101+--
102+sameStructure :: Router' env a -> Router' env b -> Bool
103+sameStructure r1 r2 =
104+ routerStructure r1 == routerStructure r2
105+
106+-- | Provide a textual representation of the
107+-- structure of a router.
108+--
109+routerLayout :: Router' env a -> Text
110+routerLayout router =
111+ T.unlines (["/"] ++ mkRouterLayout False (routerStructure router))
112+ where
113+ mkRouterLayout :: Bool -> RouterStructure -> [Text]
114+ mkRouterLayout c (StaticRouterStructure m n) = mkSubTrees c (M.toList m) n
115+ mkRouterLayout c (CaptureRouterStructure r) = mkSubTree c "<capture>" (mkRouterLayout False r)
116+ mkRouterLayout c RawRouterStructure =
117+ if c then ["├─ <raw>"] else ["└─ <raw>"]
118+ mkRouterLayout c (ChoiceStructure r1 r2) =
119+ mkRouterLayout True r1 ++ ["┆"] ++ mkRouterLayout c r2
120+
121+ mkSubTrees :: Bool -> [(Text, RouterStructure)] -> Int -> [Text]
122+ mkSubTrees _ [] 0 = []
123+ mkSubTrees c [] n =
124+ concat (replicate (n - 1) (mkLeaf True) ++ [mkLeaf c])
125+ mkSubTrees c [(t, r)] 0 =
126+ mkSubTree c t (mkRouterLayout False r)
127+ mkSubTrees c ((t, r) : trs) n =
128+ mkSubTree True t (mkRouterLayout False r) ++ mkSubTrees c trs n
129+
130+ mkLeaf :: Bool -> [Text]
131+ mkLeaf True = ["├─•","┆"]
132+ mkLeaf False = ["└─•"]
133+
134+ mkSubTree :: Bool -> Text -> [Text] -> [Text]
135+ mkSubTree True path children = ("├─ " <> path <> "/") : map ("│ " <>) children
136+ mkSubTree False path children = ("└─ " <> path <> "/") : map (" " <>) children
137+
138+-- | Apply a transformation to the response of a `Router`.
139+tweakResponse :: (RouteResult Response -> RouteResult Response) -> Router env -> Router env
140+tweakResponse f = fmap (\a -> \req cont -> a req (cont . f))
141+
56142-- | Interpret a router as an application.
57−runRouter :: Router -> RoutingApplication
58−runRouter (WithRequest router) request respond =
59− runRouter (router request) request respond
60−runRouter (StaticRouter table) request respond =
61− case pathInfo request of
62− first : rest
63− | Just router <- M.lookup first table
64− -> let request' = request { pathInfo = rest }
65− in runRouter router request' respond
66− _ -> respond $ Fail err404
67−runRouter (DynamicRouter fun) request respond =
68− case pathInfo request of
69− first : rest
70− -> let request' = request { pathInfo = rest }
71− in runRouter (fun first) request' respond
72− _ -> respond $ Fail err404
73−runRouter (LeafRouter app) request respond = app request respond
74−runRouter (Choice r1 r2) request respond =
75− runRouter r1 request $ \ mResponse1 -> case mResponse1 of
76− Fail _ -> runRouter r2 request $ \ mResponse2 ->
77− respond (highestPri mResponse1 mResponse2)
78− _ -> respond mResponse1
79− where
80− highestPri (Fail e1) (Fail e2) =
81− if worseHTTPCode (errHTTPCode e1) (errHTTPCode e2)
82− then Fail e2
83− else Fail e1
84− highestPri (Fail _) y = y
85− highestPri x _ = x
143+runRouter :: Router () -> RoutingApplication
144+runRouter r = runRouterEnv r ()
145+
146+runRouterEnv :: Router env -> env -> RoutingApplication
147+runRouterEnv router env request respond =
148+ case router of
149+ StaticRouter table ls ->
150+ case pathInfo request of
151+ [] -> runChoice ls env request respond
152+ -- This case is to handle trailing slashes.
153+ [""] -> runChoice ls env request respond
154+ first : rest | Just router' <- M.lookup first table
155+ -> let request' = request { pathInfo = rest }
156+ in runRouterEnv router' env request' respond
157+ _ -> respond $ Fail err404
158+ CaptureRouter router' ->
159+ case pathInfo request of
160+ [] -> respond $ Fail err404
161+ -- This case is to handle trailing slashes.
162+ [""] -> respond $ Fail err404
163+ first : rest
164+ -> let request' = request { pathInfo = rest }
165+ in runRouterEnv router' (first, env) request' respond
166+ RawRouter app ->
167+ app env request respond
168+ Choice r1 r2 ->
169+ runChoice [runRouterEnv r1, runRouterEnv r2] env request respond
86170
171+-- | Try a list of routing applications in order.
172+-- We stop as soon as one fails fatally or succeeds.
173+-- If all fail normally, we pick the "best" error.
174+--
175+runChoice :: [env -> RoutingApplication] -> env -> RoutingApplication
176+runChoice ls =
177+ case ls of
178+ [] -> \ _ _ respond -> respond (Fail err404)
179+ [r] -> r
180+ (r : rs) ->
181+ \ env request respond ->
182+ r env request $ \ response1 ->
183+ case response1 of
184+ Fail _ -> runChoice rs env request $ \ response2 ->
185+ respond $ highestPri response1 response2
186+ _ -> respond response1
187+ where
188+ highestPri (Fail e1) (Fail e2) =
189+ if worseHTTPCode (errHTTPCode e1) (errHTTPCode e2)
190+ then Fail e2
191+ else Fail e1
192+ highestPri (Fail _) y = y
193+ highestPri x _ = x
87194
88195-- Priority on HTTP codes.
89196--
servant-server/src/Servant/Server/Internal/RoutingApplication.hs+136−127
@@ -8,7 +8,10 @@
88{-# LANGUAGE StandaloneDeriving #-}
99module Servant.Server.Internal.RoutingApplication where
1010
11−import Control.Monad.Trans.Except (ExceptT, runExceptT)
11+import Control.Monad (ap, liftM)
12+import Control.Monad.Trans (MonadIO(..))
13+import Control.Monad.Trans.Except (runExceptT)
14+import Data.Text (Text)
1215import Network.Wai (Application, Request,
1316 Response, ResponseReceived)
1417import Prelude ()
@@ -35,31 +38,6 @@ toApplication ra request respond = ra request routingRespond
3538 routingRespond (FailFatal err) = respond $ responseServantErr err
3639 routingRespond (Route v) = respond v
3740
38−-- We currently mix up the order in which we perform checks
39−-- and the priority with which errors are reported.
40−--
41−-- For example, we perform Capture checks prior to method checks,
42−-- and therefore get 404 before 405.
43−--
44−-- However, we also perform body checks prior to method checks
45−-- now, and therefore get 415 before 405, which is wrong.
46−--
47−-- If we delay Captures, but perform method checks eagerly, we
48−-- end up potentially preferring 405 over 404, which is also bad.
49−--
50−-- So in principle, we'd like:
51−--
52−-- static routes (can cause 404)
53−-- delayed captures (can cause 404)
54−-- methods (can cause 405)
55−-- authentication and authorization (can cause 401, 403)
56−-- delayed body (can cause 415, 400)
57−-- accept header (can cause 406)
58−--
59−-- According to the HTTP decision diagram, the priority order
60−-- between HTTP status codes is as follows:
61−--
62−
6341-- | A 'Delayed' is a representation of a handler with scheduled
6442-- delayed checks that can trigger errors.
6543--
@@ -120,113 +98,139 @@ toApplication ra request respond = ra request routingRespond
12098-- The accept header check can be performed as the final
12199-- computation in this block. It can cause a 406.
122100--
123−data Delayed c where
124− Delayed :: { capturesD :: IO (RouteResult captures)
125− , methodD :: IO (RouteResult ())
126− , authD :: IO (RouteResult auth)
127− , bodyD :: IO (RouteResult body)
128− , serverD :: (captures -> auth -> body -> RouteResult c)
129− } -> Delayed c
101+data Delayed env c where
102+ Delayed :: { capturesD :: env -> DelayedIO captures
103+ , methodD :: DelayedIO ()
104+ , authD :: DelayedIO auth
105+ , bodyD :: DelayedIO body
106+ , serverD :: captures -> auth -> body -> Request -> RouteResult c
107+ } -> Delayed env c
108+
109+instance Functor (Delayed env) where
110+ fmap f Delayed{..} =
111+ Delayed
112+ { serverD = \ c a b req -> f <$> serverD c a b req
113+ , ..
114+ } -- Note [Existential Record Update]
115+
116+-- | Computations used in a 'Delayed' can depend on the
117+-- incoming 'Request', may perform 'IO, and result in a
118+-- 'RouteResult, meaning they can either suceed, fail
119+-- (with the possibility to recover), or fail fatally.
120+--
121+newtype DelayedIO a = DelayedIO { runDelayedIO :: Request -> IO (RouteResult a) }
122+
123+instance Functor DelayedIO where
124+ fmap = liftM
130125
131−instance Functor Delayed where
132− fmap f Delayed{..}
133− = Delayed { capturesD = capturesD
134− , methodD = methodD
135− , authD = authD
136− , bodyD = bodyD
137− , serverD = (fmap.fmap.fmap.fmap) f serverD
138− } -- Note [Existential Record Update]
126+instance Applicative DelayedIO where
127+ pure = return
128+ (<*>) = ap
129+
130+instance Monad DelayedIO where
131+ return x = DelayedIO (const $ return (Route x))
132+ DelayedIO m >>= f =
133+ DelayedIO $ \ req -> do
134+ r <- m req
135+ case r of
136+ Fail e -> return $ Fail e
137+ FailFatal e -> return $ FailFatal e
138+ Route a -> runDelayedIO (f a) req
139+
140+instance MonadIO DelayedIO where
141+ liftIO m = DelayedIO (const $ Route <$> m)
142+
143+-- | A 'Delayed' without any stored checks.
144+emptyDelayed :: RouteResult a -> Delayed env a
145+emptyDelayed result =
146+ Delayed (const r) r r r (\ _ _ _ _ -> result)
147+ where
148+ r = return ()
149+
150+-- | Fail with the option to recover.
151+delayedFail :: ServantErr -> DelayedIO a
152+delayedFail err = DelayedIO (const $ return $ Fail err)
153+
154+-- | Fail fatally, i.e., without any option to recover.
155+delayedFailFatal :: ServantErr -> DelayedIO a
156+delayedFailFatal err = DelayedIO (const $ return $ FailFatal err)
157+
158+-- | Gain access to the incoming request.
159+withRequest :: (Request -> DelayedIO a) -> DelayedIO a
160+withRequest f = DelayedIO (\ req -> runDelayedIO (f req) req)
139161
140162-- | Add a capture to the end of the capture block.
141−addCapture :: Delayed (a -> b)
142− -> IO (RouteResult a)
143− -> Delayed b
144−addCapture Delayed{..} new
145− = Delayed { capturesD = combineRouteResults (,) capturesD new
146− , methodD = methodD
147− , authD = authD
148− , bodyD = bodyD
149− , serverD = \ (x, v) y z -> ($ v) <$> serverD x y z
150− } -- Note [Existential Record Update]
163+addCapture :: Delayed env (a -> b)
164+ -> (Text -> DelayedIO a)
165+ -> Delayed (Text, env) b
166+addCapture Delayed{..} new =
167+ Delayed
168+ { capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt
169+ , serverD = \ (x, v) a b req -> ($ v) <$> serverD x a b req
170+ , ..
171+ } -- Note [Existential Record Update]
151172
152173-- | Add a method check to the end of the method block.
153−addMethodCheck :: Delayed a
154− -> IO (RouteResult ())
155− -> Delayed a
156−addMethodCheck Delayed{..} new
157− = Delayed { capturesD = capturesD
158− , methodD = combineRouteResults const methodD new
159− , authD = authD
160− , bodyD = bodyD
161− , serverD = serverD
162− } -- Note [Existential Record Update]
174+addMethodCheck :: Delayed env a
175+ -> DelayedIO ()
176+ -> Delayed env a
177+addMethodCheck Delayed{..} new =
178+ Delayed
179+ { methodD = methodD <* new
180+ , ..
181+ } -- Note [Existential Record Update]
163182
164183-- | Add an auth check to the end of the auth block.
165−addAuthCheck :: Delayed (a -> b)
166− -> IO (RouteResult a)
167− -> Delayed b
168−addAuthCheck Delayed{..} new
169− = Delayed { capturesD = capturesD
170− , methodD = methodD
171− , authD = combineRouteResults (,) authD new
172− , bodyD = bodyD
173− , serverD = \ x (y, v) z -> ($ v) <$> serverD x y z
174− } -- Note [Existential Record Update]
184+addAuthCheck :: Delayed env (a -> b)
185+ -> DelayedIO a
186+ -> Delayed env b
187+addAuthCheck Delayed{..} new =
188+ Delayed
189+ { authD = (,) <$> authD <*> new
190+ , serverD = \ c (y, v) b req -> ($ v) <$> serverD c y b req
191+ , ..
192+ } -- Note [Existential Record Update]
175193
176194-- | Add a body check to the end of the body block.
177−addBodyCheck :: Delayed (a -> b)
178− -> IO (RouteResult a)
179− -> Delayed b
180−addBodyCheck Delayed{..} new
181− = Delayed { capturesD = capturesD
182− , methodD = methodD
183− , authD = authD
184− , bodyD = combineRouteResults (,) bodyD new
185− , serverD = \ x y (z, v) -> ($ v) <$> serverD x y z
186− } -- Note [Existential Record Update]
195+addBodyCheck :: Delayed env (a -> b)
196+ -> DelayedIO a
197+ -> Delayed env b
198+addBodyCheck Delayed{..} new =
199+ Delayed
200+ { bodyD = (,) <$> bodyD <*> new
201+ , serverD = \ c a (z, v) req -> ($ v) <$> serverD c a z req
202+ , ..
203+ } -- Note [Existential Record Update]
187204
188205
189−-- | Add an accept header check to the end of the body block.
190−-- The accept header check should occur after the body check,
191−-- but this will be the case, because the accept header check
192−-- is only scheduled by the method combinators.
193−addAcceptCheck :: Delayed a
194− -> IO (RouteResult ())
195− -> Delayed a
196−addAcceptCheck Delayed{..} new
197− = Delayed { capturesD = capturesD
198− , methodD = methodD
199− , authD = authD
200− , bodyD = combineRouteResults const bodyD new
201− , serverD = serverD
202− } -- Note [Existential Record Update]
206+-- | Add an accept header check to the beginning of the body
207+-- block. There is a tradeoff here. In principle, we'd like
208+-- to take a bad body (400) response take precedence over a
209+-- failed accept check (406). BUT to allow streaming the body,
210+-- we cannot run the body check and then still backtrack.
211+-- We therefore do the accept check before the body check,
212+-- when we can still backtrack. There are other solutions to
213+-- this, but they'd be more complicated (such as delaying the
214+-- body check further so that it can still be run in a situation
215+-- where we'd otherwise report 406).
216+addAcceptCheck :: Delayed env a
217+ -> DelayedIO ()
218+ -> Delayed env a
219+addAcceptCheck Delayed{..} new =
220+ Delayed
221+ { bodyD = new *> bodyD
222+ , ..
223+ } -- Note [Existential Record Update]
203224
204225-- | Many combinators extract information that is passed to
205226-- the handler without the possibility of failure. In such a
206227-- case, 'passToServer' can be used.
207−passToServer :: Delayed (a -> b) -> a -> Delayed b
208−passToServer d x = ($ x) <$> d
209−
210−-- | The combination 'IO . RouteResult' is a monad, but we
211−-- don't explicitly wrap it in a newtype in order to make it
212−-- an instance. This is the '>>=' of that monad.
213−--
214−-- We stop on the first error.
215−bindRouteResults :: IO (RouteResult a) -> (a -> IO (RouteResult b)) -> IO (RouteResult b)
216−bindRouteResults m f = do
217− r <- m
218− case r of
219− Fail e -> return $ Fail e
220− FailFatal e -> return $ FailFatal e
221− Route a -> f a
222−
223−-- | Common special case of 'bindRouteResults', corresponding
224−-- to 'liftM2'.
225−combineRouteResults :: (a -> b -> c) -> IO (RouteResult a) -> IO (RouteResult b) -> IO (RouteResult c)
226−combineRouteResults f m1 m2 =
227− m1 `bindRouteResults` \ a ->
228− m2 `bindRouteResults` \ b ->
229− return (Route (f a b))
228+passToServer :: Delayed env (a -> b) -> (Request -> a) -> Delayed env b
229+passToServer Delayed{..} x =
230+ Delayed
231+ { serverD = \ c a b req -> ($ x req) <$> serverD c a b req
232+ , ..
233+ } -- Note [Existential Record Update]
230234
231235-- | Run a delayed server. Performs all scheduled operations
232236-- in order, and passes the results from the capture and body
@@ -234,24 +238,29 @@ combineRouteResults f m1 m2 =
234238--
235239-- This should only be called once per request; otherwise the guarantees about
236240-- effect and HTTP error ordering break down.
237−runDelayed :: Delayed a
241+runDelayed :: Delayed env a
242+ -> env
243+ -> Request
238244 -> IO (RouteResult a)
239−runDelayed Delayed{..} =
240− capturesD `bindRouteResults` \ c ->
241− methodD `bindRouteResults` \ _ ->
242− authD `bindRouteResults` \ a ->
243− bodyD `bindRouteResults` \ b ->
244− return (serverD c a b)
245+runDelayed Delayed{..} env = runDelayedIO $ do
246+ c <- capturesD env
247+ methodD
248+ a <- authD
249+ b <- bodyD
250+ DelayedIO (\ req -> return $ serverD c a b req)
245251
246252-- | Runs a delayed server and the resulting action.
247253-- Takes a continuation that lets us send a response.
248254-- Also takes a continuation for how to turn the
249255-- result of the delayed server into a response.
250−runAction :: Delayed (ExceptT ServantErr IO a)
256+runAction :: Delayed env (Handler a)
257+ -> env
258+ -> Request
251259 -> (RouteResult Response -> IO r)
252260 -> (a -> RouteResult Response)
253261 -> IO r
254−runAction action respond k = runDelayed action >>= go >>= respond
262+runAction action env req respond k =
263+ runDelayed action env req >>= go >>= respond
255264 where
256265 go (Fail e) = return $ Fail e
257266 go (FailFatal e) = return $ FailFatal e
servant-server/src/Servant/Server/Internal/ServantErr.hs+63−60
@@ -4,6 +4,7 @@
44module Servant.Server.Internal.ServantErr where
55
66import Control.Exception (Exception)
7+import Control.Monad.Trans.Except (ExceptT)
78import qualified Data.ByteString.Char8 as BS
89import qualified Data.ByteString.Lazy as LBS
910import Data.Typeable (Typeable)
@@ -18,6 +19,8 @@ data ServantErr = ServantErr { errHTTPCode :: Int
1819
1920instance Exception ServantErr
2021
22+type Handler = ExceptT ServantErr IO
23+
2124responseServantErr :: ServantErr -> Response
2225responseServantErr ServantErr{..} = responseLBS status errHeaders errBody
2326 where
@@ -27,8 +30,8 @@ responseServantErr ServantErr{..} = responseLBS status errHeaders errBody
2730--
2831-- Example:
2932--
30−-- > failingHandler :: ExceptT ServantErr IO ()
31−-- > failingHandler = throwErr $ err300 { errBody = "I can't choose." }
33+-- > failingHandler :: Handler ()
34+-- > failingHandler = throwError $ err300 { errBody = "I can't choose." }
3235--
3336err300 :: ServantErr
3437err300 = ServantErr { errHTTPCode = 300
@@ -41,8 +44,8 @@ err300 = ServantErr { errHTTPCode = 300
4144--
4245-- Example:
4346--
44−-- > failingHandler :: ExceptT ServantErr IO ()
45−-- > failingHandler = throwErr err301
47+-- > failingHandler :: Handler ()
48+-- > failingHandler = throwError err301
4649--
4750err301 :: ServantErr
4851err301 = ServantErr { errHTTPCode = 301
@@ -55,8 +58,8 @@ err301 = ServantErr { errHTTPCode = 301
5558--
5659-- Example:
5760--
58−-- > failingHandler :: ExceptT ServantErr IO ()
59−-- > failingHandler = throwErr err302
61+-- > failingHandler :: Handler ()
62+-- > failingHandler = throwError err302
6063--
6164err302 :: ServantErr
6265err302 = ServantErr { errHTTPCode = 302
@@ -69,8 +72,8 @@ err302 = ServantErr { errHTTPCode = 302
6972--
7073-- Example:
7174--
72−-- > failingHandler :: ExceptT ServantErr IO ()
73−-- > failingHandler = throwErr err303
75+-- > failingHandler :: Handler ()
76+-- > failingHandler = throwError err303
7477--
7578err303 :: ServantErr
7679err303 = ServantErr { errHTTPCode = 303
@@ -83,8 +86,8 @@ err303 = ServantErr { errHTTPCode = 303
8386--
8487-- Example:
8588--
86−-- > failingHandler :: ExceptT ServantErr IO ()
87−-- > failingHandler = throwErr err304
89+-- > failingHandler :: Handler ()
90+-- > failingHandler = throwError err304
8891--
8992err304 :: ServantErr
9093err304 = ServantErr { errHTTPCode = 304
@@ -97,8 +100,8 @@ err304 = ServantErr { errHTTPCode = 304
97100--
98101-- Example:
99102--
100−-- > failingHandler :: ExceptT ServantErr IO ()
101−-- > failingHandler = throwErr err305
103+-- > failingHandler :: Handler ()
104+-- > failingHandler = throwError err305
102105--
103106err305 :: ServantErr
104107err305 = ServantErr { errHTTPCode = 305
@@ -111,8 +114,8 @@ err305 = ServantErr { errHTTPCode = 305
111114--
112115-- Example:
113116--
114−-- > failingHandler :: ExceptT ServantErr IO ()
115−-- > failingHandler = throwErr err307
117+-- > failingHandler :: Handler ()
118+-- > failingHandler = throwError err307
116119--
117120err307 :: ServantErr
118121err307 = ServantErr { errHTTPCode = 307
@@ -125,8 +128,8 @@ err307 = ServantErr { errHTTPCode = 307
125128--
126129-- Example:
127130--
128−-- > failingHandler :: ExceptT ServantErr IO ()
129−-- > failingHandler = throwErr $ err400 { errBody = "Your request makes no sense to me." }
131+-- > failingHandler :: Handler ()
132+-- > failingHandler = throwError $ err400 { errBody = "Your request makes no sense to me." }
130133--
131134err400 :: ServantErr
132135err400 = ServantErr { errHTTPCode = 400
@@ -139,8 +142,8 @@ err400 = ServantErr { errHTTPCode = 400
139142--
140143-- Example:
141144--
142−-- > failingHandler :: ExceptT ServantErr IO ()
143−-- > failingHandler = throwErr $ err401 { errBody = "Your credentials are invalid." }
145+-- > failingHandler :: Handler ()
146+-- > failingHandler = throwError $ err401 { errBody = "Your credentials are invalid." }
144147--
145148err401 :: ServantErr
146149err401 = ServantErr { errHTTPCode = 401
@@ -153,8 +156,8 @@ err401 = ServantErr { errHTTPCode = 401
153156--
154157-- Example:
155158--
156−-- > failingHandler :: ExceptT ServantErr IO ()
157−-- > failingHandler = throwErr $ err402 { errBody = "You have 0 credits. Please give me $$$." }
159+-- > failingHandler :: Handler ()
160+-- > failingHandler = throwError $ err402 { errBody = "You have 0 credits. Please give me $$$." }
158161--
159162err402 :: ServantErr
160163err402 = ServantErr { errHTTPCode = 402
@@ -167,8 +170,8 @@ err402 = ServantErr { errHTTPCode = 402
167170--
168171-- Example:
169172--
170−-- > failingHandler :: ExceptT ServantErr IO ()
171−-- > failingHandler = throwErr $ err403 { errBody = "Please login first." }
173+-- > failingHandler :: Handler ()
174+-- > failingHandler = throwError $ err403 { errBody = "Please login first." }
172175--
173176err403 :: ServantErr
174177err403 = ServantErr { errHTTPCode = 403
@@ -181,8 +184,8 @@ err403 = ServantErr { errHTTPCode = 403
181184--
182185-- Example:
183186--
184−-- > failingHandler :: ExceptT ServantErr IO ()
185−-- > failingHandler = throwErr $ err404 { errBody = "(╯°□°)╯︵ ┻━┻)." }
187+-- > failingHandler :: Handler ()
188+-- > failingHandler = throwError $ err404 { errBody = "(╯°□°)╯︵ ┻━┻)." }
186189--
187190err404 :: ServantErr
188191err404 = ServantErr { errHTTPCode = 404
@@ -195,8 +198,8 @@ err404 = ServantErr { errHTTPCode = 404
195198--
196199-- Example:
197200--
198−-- > failingHandler :: ExceptT ServantErr IO ()
199−-- > failingHandler = throwErr $ err405 { errBody = "Your account privileges does not allow for this. Please pay $$$." }
201+-- > failingHandler :: Handler ()
202+-- > failingHandler = throwError $ err405 { errBody = "Your account privileges does not allow for this. Please pay $$$." }
200203--
201204err405 :: ServantErr
202205err405 = ServantErr { errHTTPCode = 405
@@ -209,8 +212,8 @@ err405 = ServantErr { errHTTPCode = 405
209212--
210213-- Example:
211214--
212−-- > failingHandler :: ExceptT ServantErr IO ()
213−-- > failingHandler = throwErr err406
215+-- > failingHandler :: Handler ()
216+-- > failingHandler = throwError err406
214217--
215218err406 :: ServantErr
216219err406 = ServantErr { errHTTPCode = 406
@@ -223,8 +226,8 @@ err406 = ServantErr { errHTTPCode = 406
223226--
224227-- Example:
225228--
226−-- > failingHandler :: ExceptT ServantErr IO ()
227−-- > failingHandler = throwErr err407
229+-- > failingHandler :: Handler ()
230+-- > failingHandler = throwError err407
228231--
229232err407 :: ServantErr
230233err407 = ServantErr { errHTTPCode = 407
@@ -237,8 +240,8 @@ err407 = ServantErr { errHTTPCode = 407
237240--
238241-- Example:
239242--
240−-- > failingHandler :: ExceptT ServantErr IO ()
241−-- > failingHandler = throwErr $ err409 { errBody = "Transaction conflicts with 59879cb56c7c159231eeacdd503d755f7e835f74" }
243+-- > failingHandler :: Handler ()
244+-- > failingHandler = throwError $ err409 { errBody = "Transaction conflicts with 59879cb56c7c159231eeacdd503d755f7e835f74" }
242245--
243246err409 :: ServantErr
244247err409 = ServantErr { errHTTPCode = 409
@@ -251,8 +254,8 @@ err409 = ServantErr { errHTTPCode = 409
251254--
252255-- Example:
253256--
254−-- > failingHandler :: ExceptT ServantErr IO ()
255−-- > failingHandler = throwErr $ err410 { errBody = "I know it was here at some point, but.. I blame bad luck." }
257+-- > failingHandler :: Handler ()
258+-- > failingHandler = throwError $ err410 { errBody = "I know it was here at some point, but.. I blame bad luck." }
256259--
257260err410 :: ServantErr
258261err410 = ServantErr { errHTTPCode = 410
@@ -265,8 +268,8 @@ err410 = ServantErr { errHTTPCode = 410
265268--
266269-- Example:
267270--
268−-- > failingHandler :: ExceptT ServantErr IO ()
269−-- > failingHandler = throwErr err411
271+-- > failingHandler :: Handler ()
272+-- > failingHandler = throwError err411
270273--
271274err411 :: ServantErr
272275err411 = ServantErr { errHTTPCode = 411
@@ -279,8 +282,8 @@ err411 = ServantErr { errHTTPCode = 411
279282--
280283-- Example:
281284--
282−-- > failingHandler :: ExceptT ServantErr IO ()
283−-- > failingHandler = throwErr $ err412 { errBody = "Precondition fail: x < 42 && y > 57" }
285+-- > failingHandler :: Handler ()
286+-- > failingHandler = throwError $ err412 { errBody = "Precondition fail: x < 42 && y > 57" }
284287--
285288err412 :: ServantErr
286289err412 = ServantErr { errHTTPCode = 412
@@ -293,8 +296,8 @@ err412 = ServantErr { errHTTPCode = 412
293296--
294297-- Example:
295298--
296−-- > failingHandler :: ExceptT ServantErr IO ()
297−-- > failingHandler = throwErr $ err413 { errBody = "Request exceeded 64k." }
299+-- > failingHandler :: Handler ()
300+-- > failingHandler = throwError $ err413 { errBody = "Request exceeded 64k." }
298301--
299302err413 :: ServantErr
300303err413 = ServantErr { errHTTPCode = 413
@@ -307,8 +310,8 @@ err413 = ServantErr { errHTTPCode = 413
307310--
308311-- Example:
309312--
310−-- > failingHandler :: ExceptT ServantErr IO ()
311−-- > failingHandler = throwErr $ err414 { errBody = "Maximum length is 64." }
313+-- > failingHandler :: Handler ()
314+-- > failingHandler = throwError $ err414 { errBody = "Maximum length is 64." }
312315--
313316err414 :: ServantErr
314317err414 = ServantErr { errHTTPCode = 414
@@ -321,8 +324,8 @@ err414 = ServantErr { errHTTPCode = 414
321324--
322325-- Example:
323326--
324−-- > failingHandler :: ExceptT ServantErr IO ()
325−-- > failingHandler = throwErr $ err415 { errBody = "Supported media types: gif, png" }
327+-- > failingHandler :: Handler ()
328+-- > failingHandler = throwError $ err415 { errBody = "Supported media types: gif, png" }
326329--
327330err415 :: ServantErr
328331err415 = ServantErr { errHTTPCode = 415
@@ -335,8 +338,8 @@ err415 = ServantErr { errHTTPCode = 415
335338--
336339-- Example:
337340--
338−-- > failingHandler :: ExceptT ServantErr IO ()
339−-- > failingHandler = throwErr $ err416 { errBody = "Valid range is [0, 424242]." }
341+-- > failingHandler :: Handler ()
342+-- > failingHandler = throwError $ err416 { errBody = "Valid range is [0, 424242]." }
340343--
341344err416 :: ServantErr
342345err416 = ServantErr { errHTTPCode = 416
@@ -349,8 +352,8 @@ err416 = ServantErr { errHTTPCode = 416
349352--
350353-- Example:
351354--
352−-- > failingHandler :: ExceptT ServantErr IO ()
353−-- > failingHandler = throwErr $ err417 { errBody = "I found a quux in the request. This isn't going to work." }
355+-- > failingHandler :: Handler ()
356+-- > failingHandler = throwError $ err417 { errBody = "I found a quux in the request. This isn't going to work." }
354357--
355358err417 :: ServantErr
356359err417 = ServantErr { errHTTPCode = 417
@@ -363,8 +366,8 @@ err417 = ServantErr { errHTTPCode = 417
363366--
364367-- Example:
365368--
366−-- > failingHandler :: ExceptT ServantErr IO ()
367−-- > failingHandler = throwErr $ err500 { errBody = "Exception in module A.B.C:55. Have a great day!" }
369+-- > failingHandler :: Handler ()
370+-- > failingHandler = throwError $ err500 { errBody = "Exception in module A.B.C:55. Have a great day!" }
368371--
369372err500 :: ServantErr
370373err500 = ServantErr { errHTTPCode = 500
@@ -377,8 +380,8 @@ err500 = ServantErr { errHTTPCode = 500
377380--
378381-- Example:
379382--
380−-- > failingHandler :: ExceptT ServantErr IO ()
381−-- > failingHandler = throwErr $ err501 { errBody = "/v1/foo is not supported with quux in the request." }
383+-- > failingHandler :: Handler ()
384+-- > failingHandler = throwError $ err501 { errBody = "/v1/foo is not supported with quux in the request." }
382385--
383386err501 :: ServantErr
384387err501 = ServantErr { errHTTPCode = 501
@@ -391,8 +394,8 @@ err501 = ServantErr { errHTTPCode = 501
391394--
392395-- Example:
393396--
394−-- > failingHandler :: ExceptT ServantErr IO ()
395−-- > failingHandler = throwErr $ err502 { errBody = "Tried gateway foo, bar, and baz. None responded." }
397+-- > failingHandler :: Handler ()
398+-- > failingHandler = throwError $ err502 { errBody = "Tried gateway foo, bar, and baz. None responded." }
396399--
397400err502 :: ServantErr
398401err502 = ServantErr { errHTTPCode = 502
@@ -405,8 +408,8 @@ err502 = ServantErr { errHTTPCode = 502
405408--
406409-- Example:
407410--
408−-- > failingHandler :: ExceptT ServantErr IO ()
409−-- > failingHandler = throwErr $ err503 { errBody = "We're rewriting in PHP." }
411+-- > failingHandler :: Handler ()
412+-- > failingHandler = throwError $ err503 { errBody = "We're rewriting in PHP." }
410413--
411414err503 :: ServantErr
412415err503 = ServantErr { errHTTPCode = 503
@@ -419,8 +422,8 @@ err503 = ServantErr { errHTTPCode = 503
419422--
420423-- Example:
421424--
422−-- > failingHandler :: ExceptT ServantErr IO ()
423−-- > failingHandler = throwErr $ err504 { errBody = "Backend foobar did not respond in 5 seconds." }
425+-- > failingHandler :: Handler ()
426+-- > failingHandler = throwError $ err504 { errBody = "Backend foobar did not respond in 5 seconds." }
424427--
425428err504 :: ServantErr
426429err504 = ServantErr { errHTTPCode = 504
@@ -433,8 +436,8 @@ err504 = ServantErr { errHTTPCode = 504
433436--
434437-- Example usage:
435438--
436−-- > failingHandler :: ExceptT ServantErr IO ()
437−-- > failingHandler = throwErr $ err505 { errBody = "I support HTTP/4.0 only." }
439+-- > failingHandler :: Handler ()
440+-- > failingHandler = throwError $ err505 { errBody = "I support HTTP/4.0 only." }
438441--
439442err505 :: ServantErr
440443err505 = ServantErr { errHTTPCode = 505
servant-server/test/Servant/Server/Internal/EnterSpec.hs → servant-server/test/Servant/ArbitraryMonadServerSpec.hsrenamed+3−4
@@ -1,11 +1,10 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE OverloadedStrings #-}
33{-# LANGUAGE TypeOperators #-}
4−module Servant.Server.Internal.EnterSpec where
4+module Servant.ArbitraryMonadServerSpec where
55
66import qualified Control.Category as C
77import Control.Monad.Reader
8−import Control.Monad.Trans.Except
98import Data.Proxy
109import Servant.API
1110import Servant.Server
@@ -15,7 +14,7 @@ import Test.Hspec.Wai (get, matchStatus, post,
1514 shouldRespondWith, with)
1615
1716spec :: Spec
18−spec = describe "module Servant.Server.Enter" $ do
17+spec = describe "Arbitrary monad server" $ do
1918 enterSpec
2019
2120type ReaderAPI = "int" :> Get '[JSON] Int
@@ -34,7 +33,7 @@ combinedAPI = Proxy
3433readerServer' :: ServerT ReaderAPI (Reader String)
3534readerServer' = return 1797 :<|> ask
3635
37−fReader :: Reader String :~> ExceptT ServantErr IO
36+fReader :: Reader String :~> Handler
3837fReader = generalizeNat C.. (runReaderTNat "hi")
3938
4039readerServer :: Server ReaderAPI
servant-server/test/Servant/Server/ErrorSpec.hs+24−7
@@ -53,6 +53,23 @@ errorOrderApi = Proxy
5353errorOrderServer :: Server ErrorOrderApi
5454errorOrderServer = \_ _ _ -> throwE err402
5555
56+-- On error priorities:
57+--
58+-- We originally had
59+--
60+-- 404, 405, 401, 415, 400, 406, 402
61+--
62+-- but we changed this to
63+--
64+-- 404, 405, 401, 406, 415, 400, 402
65+--
66+-- for servant-0.7.
67+--
68+-- This change is due to the body check being irreversible (to support
69+-- streaming). Any check done after the body check has to be made fatal,
70+-- breaking modularity. We've therefore moved the accept check before
71+-- the body check, to allow it being recoverable and modular, and this
72+-- goes along with promoting the error priority of 406.
5673errorOrderSpec :: Spec
5774errorOrderSpec =
5875 describe "HTTP error order" $
@@ -86,18 +103,18 @@ errorOrderSpec =
86103 request goodMethod goodUrl [badAuth, badContentType, badAccept] badBody
87104 `shouldRespondWith` 401
88105
89− it "has 415 as its fourth highest priority error" $ do
106+ it "has 406 as its fourth highest priority error" $ do
90107 request goodMethod goodUrl [goodAuth, badContentType, badAccept] badBody
108+ `shouldRespondWith` 406
109+
110+ it "has 415 as its fifth highest priority error" $ do
111+ request goodMethod goodUrl [goodAuth, badContentType, goodAccept] badBody
91112 `shouldRespondWith` 415
92113
93− it "has 400 as its fifth highest priority error" $ do
94− request goodMethod goodUrl [goodAuth, goodContentType, badAccept] badBody
114+ it "has 400 as its sixth highest priority error" $ do
115+ request goodMethod goodUrl [goodAuth, goodContentType, goodAccept] badBody
95116 `shouldRespondWith` 400
96117
97− it "has 406 as its sixth highest priority error" $ do
98− request goodMethod goodUrl [goodAuth, goodContentType, badAccept] goodBody
99− `shouldRespondWith` 406
100−
101118 it "has handler-level errors as last priority" $ do
102119 request goodMethod goodUrl [goodAuth, goodContentType, goodAccept] goodBody
103120 `shouldRespondWith` 402
servant-server/test/Servant/Server/Internal/ContextSpec.hs+5−4
@@ -1,9 +1,9 @@
11{-# LANGUAGE DataKinds #-}
2−{-# OPTIONS_GHC -fdefer-type-errors #-}
2+{-# OPTIONS_GHC -fdefer-type-errors -Wwarn #-}
33module Servant.Server.Internal.ContextSpec (spec) where
44
55import Data.Proxy (Proxy (..))
6−import Test.Hspec (Spec, describe, it, shouldBe, pending, context)
6+import Test.Hspec (Spec, describe, it, shouldBe, context)
77import Test.ShouldNotTypecheck (shouldNotTypecheck)
88
99import Servant.API
@@ -26,16 +26,17 @@ spec = do
2626 shouldNotTypecheck x
2727
2828 context "Show instance" $ do
29− let cxt = 'a' :. True :. EmptyContext
3029 it "has a Show instance" $ do
30+ let cxt = 'a' :. True :. EmptyContext
3131 show cxt `shouldBe` "'a' :. True :. EmptyContext"
3232
3333 context "bracketing" $ do
3434 it "works" $ do
35+ let cxt = 'a' :. True :. EmptyContext
3536 show (Just cxt) `shouldBe` "Just ('a' :. True :. EmptyContext)"
3637
3738 it "works with operators" $ do
38− let cxt = (1 :. 'a' :. EmptyContext) :<|> ('b' :. True :. EmptyContext)
39+ let cxt = ((1 :: Integer) :. 'a' :. EmptyContext) :<|> ('b' :. True :. EmptyContext)
3940 show cxt `shouldBe` "(1 :. 'a' :. EmptyContext) :<|> ('b' :. True :. EmptyContext)"
4041
4142 describe "descendIntoNamedContext" $ do
servant-server/test/Servant/Server/RouterSpec.hsnew file+294
@@ -0,0 +1,294 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE TypeOperators #-}
5+module Servant.Server.RouterSpec (spec) where
6+
7+import Control.Monad (unless)
8+import Data.Proxy (Proxy(..))
9+import Data.Text (unpack)
10+import Network.HTTP.Types (Status (..))
11+import Network.Wai (responseBuilder)
12+import Network.Wai.Internal (Response (ResponseBuilder))
13+import Test.Hspec
14+import Test.Hspec.Wai (get, shouldRespondWith, with)
15+import Servant.API
16+import Servant.Server
17+import Servant.Server.Internal
18+
19+spec :: Spec
20+spec = describe "Servant.Server.Internal.Router" $ do
21+ routerSpec
22+ distributivitySpec
23+
24+routerSpec :: Spec
25+routerSpec = do
26+ let app' :: Application
27+ app' = toApplication $ runRouter router'
28+
29+ router', router :: Router ()
30+ router' = tweakResponse (fmap twk) router
31+ router = leafRouter $ \_ _ cont -> cont (Route $ responseBuilder (Status 201 "") [] "")
32+
33+ twk :: Response -> Response
34+ twk (ResponseBuilder (Status i s) hs b) = ResponseBuilder (Status (i + 1) s) hs b
35+ twk b = b
36+
37+ describe "tweakResponse" . with (return app') $ do
38+ it "calls f on route result" $ do
39+ get "" `shouldRespondWith` 202
40+
41+distributivitySpec :: Spec
42+distributivitySpec =
43+ describe "choice" $ do
44+ it "distributes endpoints through static paths" $ do
45+ endpoint `shouldHaveSameStructureAs` endpointRef
46+ it "distributes nested routes through static paths" $ do
47+ static `shouldHaveSameStructureAs` staticRef
48+ it "distributes nested routes through dynamic paths" $ do
49+ dynamic `shouldHaveSameStructureAs` dynamicRef
50+ it "properly reorders permuted static paths" $ do
51+ permute `shouldHaveSameStructureAs` permuteRef
52+ it "properly reorders permuted static paths in the presence of Raw in end" $ do
53+ permuteRawEnd `shouldHaveSameStructureAs` permuteRawEndRef
54+ it "properly reorders permuted static paths in the presence of Raw in beginning" $ do
55+ permuteRawBegin `shouldHaveSameStructureAs` permuteRawBeginRef
56+ it "properly reorders permuted static paths in the presence of Raw in middle" $ do
57+ permuteRawMiddle `shouldHaveSameStructureAs` permuteRawMiddleRef
58+ it "properly reorders permuted static paths in the presence of a root endpoint in end" $ do
59+ permuteEndEnd `shouldHaveSameStructureAs` permuteEndRef
60+ it "properly reorders permuted static paths in the presence of a root endpoint in beginning" $ do
61+ permuteEndBegin `shouldHaveSameStructureAs` permuteEndRef
62+ it "properly reorders permuted static paths in the presence of a root endpoint in middle" $ do
63+ permuteEndMiddle `shouldHaveSameStructureAs` permuteEndRef
64+ it "properly handles mixing static paths at different levels" $ do
65+ level `shouldHaveSameStructureAs` levelRef
66+
67+shouldHaveSameStructureAs ::
68+ (HasServer api1 '[], HasServer api2 '[]) => Proxy api1 -> Proxy api2 -> Expectation
69+shouldHaveSameStructureAs p1 p2 =
70+ unless (sameStructure (makeTrivialRouter p1) (makeTrivialRouter p2)) $
71+ expectationFailure ("expected:\n" ++ unpack (layout p2) ++ "\nbut got:\n" ++ unpack (layout p1))
72+
73+makeTrivialRouter :: (HasServer layout '[]) => Proxy layout -> Router ()
74+makeTrivialRouter p =
75+ route p EmptyContext (emptyDelayed (FailFatal err501))
76+
77+type End = Get '[JSON] ()
78+
79+-- The latter version looks more efficient,
80+-- but the former should be compiled to the
81+-- same layout:
82+
83+type Endpoint = "a" :> End :<|> "a" :> End
84+type EndpointRef = "a" :> (End :<|> End)
85+
86+endpoint :: Proxy Endpoint
87+endpoint = Proxy
88+
89+endpointRef :: Proxy EndpointRef
90+endpointRef = Proxy
91+
92+-- Again, the latter version looks more efficient,
93+-- but the former should be compiled to the same
94+-- layout:
95+
96+type Static = "a" :> "b" :> End :<|> "a" :> "c" :> End
97+type StaticRef = "a" :> ("b" :> End :<|> "c" :> End)
98+
99+static :: Proxy Static
100+static = Proxy
101+
102+staticRef :: Proxy StaticRef
103+staticRef = Proxy
104+
105+-- Even for dynamic path components, we expect the
106+-- router to simplify the layout, because captures
107+-- are delayed and only actually performed once
108+-- reaching an endpoint. So the former version and
109+-- the latter should be compiled to the same router
110+-- structure:
111+
112+type Dynamic =
113+ "a" :> Capture "foo" Int :> "b" :> End
114+ :<|> "a" :> Capture "bar" Bool :> "c" :> End
115+ :<|> "a" :> Capture "baz" Char :> "d" :> End
116+
117+type DynamicRef =
118+ "a" :> Capture "anything" () :>
119+ ("b" :> End :<|> "c" :> End :<|> "d" :> End)
120+
121+dynamic :: Proxy Dynamic
122+dynamic = Proxy
123+
124+dynamicRef :: Proxy DynamicRef
125+dynamicRef = Proxy
126+
127+-- A more complicated example of static route reordering.
128+-- All the permuted paths should be correctly grouped,
129+-- so both 'Permute' and 'PermuteRef' should compile to
130+-- the same layout:
131+
132+type Permute =
133+ "a" :> "b" :> "c" :> End
134+ :<|> "b" :> "a" :> "c" :> End
135+ :<|> "a" :> "c" :> "b" :> End
136+ :<|> "c" :> "a" :> "b" :> End
137+ :<|> "b" :> "c" :> "a" :> End
138+ :<|> "c" :> "b" :> "a" :> End
139+
140+type PermuteRef =
141+ "a" :> ( "b" :> "c" :> End
142+ :<|> "c" :> "b" :> End
143+ )
144+ :<|> "b" :> ( "a" :> "c" :> End
145+ :<|> "c" :> "a" :> End
146+ )
147+ :<|> "c" :> ( "a" :> "b" :> End
148+ :<|> "b" :> "a" :> End
149+ )
150+
151+permute :: Proxy Permute
152+permute = Proxy
153+
154+permuteRef :: Proxy PermuteRef
155+permuteRef = Proxy
156+
157+-- Adding a 'Raw' in one of the ends should have minimal
158+-- effect on the grouping.
159+
160+type PermuteRawEnd =
161+ "a" :> "b" :> "c" :> End
162+ :<|> "b" :> "a" :> "c" :> End
163+ :<|> "a" :> "c" :> "b" :> End
164+ :<|> "c" :> "a" :> "b" :> End
165+ :<|> "b" :> "c" :> "a" :> End
166+ :<|> "c" :> "b" :> "a" :> End
167+ :<|> Raw
168+
169+type PermuteRawEndRef = PermuteRef :<|> Raw
170+
171+type PermuteRawBegin =
172+ Raw
173+ :<|> "a" :> "b" :> "c" :> End
174+ :<|> "b" :> "a" :> "c" :> End
175+ :<|> "a" :> "c" :> "b" :> End
176+ :<|> "c" :> "a" :> "b" :> End
177+ :<|> "b" :> "c" :> "a" :> End
178+ :<|> "c" :> "b" :> "a" :> End
179+
180+type PermuteRawBeginRef = Raw :<|> PermuteRef
181+
182+permuteRawBegin :: Proxy PermuteRawBegin
183+permuteRawBegin = Proxy
184+
185+permuteRawBeginRef :: Proxy PermuteRawBeginRef
186+permuteRawBeginRef = Proxy
187+
188+permuteRawEnd :: Proxy PermuteRawEnd
189+permuteRawEnd = Proxy
190+
191+permuteRawEndRef :: Proxy PermuteRawEndRef
192+permuteRawEndRef = Proxy
193+
194+-- Adding a 'Raw' in the middle will disrupt grouping,
195+-- because we commute things past a 'Raw'. But the two
196+-- halves should still be grouped.
197+
198+type PermuteRawMiddle =
199+ "a" :> "b" :> "c" :> End
200+ :<|> "b" :> "a" :> "c" :> End
201+ :<|> "a" :> "c" :> "b" :> End
202+ :<|> Raw
203+ :<|> "c" :> "a" :> "b" :> End
204+ :<|> "b" :> "c" :> "a" :> End
205+ :<|> "c" :> "b" :> "a" :> End
206+
207+type PermuteRawMiddleRef =
208+ "a" :> ( "b" :> "c" :> End
209+ :<|> "c" :> "b" :> End
210+ )
211+ :<|> "b" :> "a" :> "c" :> End
212+ :<|> Raw
213+ :<|> "b" :> "c" :> "a" :> End
214+ :<|> "c" :> ( "a" :> "b" :> End
215+ :<|> "b" :> "a" :> End
216+ )
217+
218+permuteRawMiddle :: Proxy PermuteRawMiddle
219+permuteRawMiddle = Proxy
220+
221+permuteRawMiddleRef :: Proxy PermuteRawMiddleRef
222+permuteRawMiddleRef = Proxy
223+
224+-- Adding an endpoint at the top-level in various places
225+-- is also somewhat critical for grouping, but it should
226+-- not disrupt grouping at all, even if it is placed in
227+-- the middle.
228+
229+type PermuteEndEnd =
230+ "a" :> "b" :> "c" :> End
231+ :<|> "b" :> "a" :> "c" :> End
232+ :<|> "a" :> "c" :> "b" :> End
233+ :<|> "c" :> "a" :> "b" :> End
234+ :<|> "b" :> "c" :> "a" :> End
235+ :<|> "c" :> "b" :> "a" :> End
236+ :<|> End
237+
238+type PermuteEndBegin =
239+ End
240+ :<|> "a" :> "b" :> "c" :> End
241+ :<|> "b" :> "a" :> "c" :> End
242+ :<|> "a" :> "c" :> "b" :> End
243+ :<|> "c" :> "a" :> "b" :> End
244+ :<|> "b" :> "c" :> "a" :> End
245+ :<|> "c" :> "b" :> "a" :> End
246+
247+type PermuteEndMiddle =
248+ "a" :> "b" :> "c" :> End
249+ :<|> "b" :> "a" :> "c" :> End
250+ :<|> "a" :> "c" :> "b" :> End
251+ :<|> End
252+ :<|> "c" :> "a" :> "b" :> End
253+ :<|> "b" :> "c" :> "a" :> End
254+ :<|> "c" :> "b" :> "a" :> End
255+
256+type PermuteEndRef = PermuteRef :<|> End
257+
258+permuteEndEnd :: Proxy PermuteEndEnd
259+permuteEndEnd = Proxy
260+
261+permuteEndBegin :: Proxy PermuteEndBegin
262+permuteEndBegin = Proxy
263+
264+permuteEndMiddle :: Proxy PermuteEndMiddle
265+permuteEndMiddle = Proxy
266+
267+permuteEndRef :: Proxy PermuteEndRef
268+permuteEndRef = Proxy
269+
270+-- An API with routes on different nesting levels that
271+-- is composed out of different fragments should still
272+-- be reordered correctly.
273+
274+type LevelFragment1 =
275+ "a" :> "b" :> End
276+ :<|> "a" :> End
277+
278+type LevelFragment2 =
279+ "b" :> End
280+ :<|> "a" :> "c" :> End
281+ :<|> End
282+
283+type Level = LevelFragment1 :<|> LevelFragment2
284+
285+type LevelRef =
286+ "a" :> ("b" :> End :<|> "c" :> End :<|> End)
287+ :<|> "b" :> End
288+ :<|> End
289+
290+level :: Proxy Level
291+level = Proxy
292+
293+levelRef :: Proxy LevelRef
294+levelRef = Proxy
servant-server/test/Servant/Server/StreamingSpec.hs+2−3
@@ -9,9 +9,8 @@
99module Servant.Server.StreamingSpec where
1010
1111import Control.Concurrent
12−import Control.Exception
12+import Control.Exception hiding (Handler)
1313import Control.Monad.IO.Class
14−import Control.Monad.Trans.Except
1514import qualified Data.ByteString as Strict
1615import qualified Data.ByteString.Lazy as Lazy
1716import Network.HTTP.Types
@@ -66,7 +65,7 @@ spec = do
6665 -- - receives the first chunk
6766 -- - notifies serverReceivedFirstChunk
6867 -- - receives the rest of the request
69− let handler :: Lazy.ByteString -> ExceptT ServantErr IO NoContent
68+ let handler :: Lazy.ByteString -> Handler NoContent
7069 handler input = liftIO $ do
7170 let prefix = Lazy.take 3 input
7271 prefix `shouldBe` "foo"
servant-server/test/Servant/Server/UsingContextSpec.hs+1−2
@@ -5,7 +5,6 @@
55
66module Servant.Server.UsingContextSpec where
77
8−import Control.Monad.Trans.Except
98import Network.Wai
109import Test.Hspec (Spec, describe, it)
1110import Test.Hspec.Wai
@@ -25,7 +24,7 @@ spec = do
2524type OneEntryAPI =
2625 ExtractFromContext :> Get '[JSON] String
2726
28−testServer :: String -> ExceptT ServantErr IO String
27+testServer :: String -> Handler String
2928testServer s = return s
3029
3130oneEntryApp :: Application
servant-server/test/Servant/Server/UsingContextSpec/TestCombinators.hs+2−3
@@ -20,7 +20,6 @@ module Servant.Server.UsingContextSpec.TestCombinators where
2020import GHC.TypeLits
2121
2222import Servant
23−import Servant.Server.Internal.RoutingApplication
2423
2524data ExtractFromContext
2625
@@ -31,12 +30,12 @@ instance (HasContextEntry context String, HasServer subApi context) =>
3130 String -> ServerT subApi m
3231
3332 route Proxy context delayed =
34− route subProxy context (fmap (inject context) delayed :: Delayed (Server subApi))
33+ route subProxy context (fmap inject delayed)
3534 where
3635 subProxy :: Proxy subApi
3736 subProxy = Proxy
3837
39− inject context f = f (getContextEntry context)
38+ inject f = f (getContextEntry context)
4039
4140data InjectIntoContext
4241
servant-server/test/Servant/ServerSpec.hs+65−58
@@ -13,14 +13,13 @@
1313
1414module Servant.ServerSpec where
1515
16−#if !MIN_VERSION_base(4,8,0)
17−import Control.Applicative ((<$>))
18−#endif
1916import Control.Monad (forM_, when, unless)
20−import Control.Monad.Trans.Except (ExceptT, throwE)
17+import Control.Monad.Trans.Except (throwE)
2118import Data.Aeson (FromJSON, ToJSON, decode', encode)
19+import qualified Data.ByteString.Base64 as Base64
2220import Data.ByteString.Conversion ()
2321import Data.Char (toUpper)
22+import Data.Monoid
2423import Data.Proxy (Proxy (Proxy))
2524import Data.String (fromString)
2625import Data.String.Conversions (cs)
@@ -30,11 +29,11 @@ import Network.HTTP.Types (Status (..), hAccept, hContentType,
3029 methodDelete, methodGet,
3130 methodHead, methodPatch,
3231 methodPost, methodPut, ok200,
32+ imATeaPot418,
3333 parseQuery)
3434import Network.Wai (Application, Request, requestHeaders, pathInfo,
3535 queryString, rawQueryString,
36− responseBuilder, responseLBS)
37−import Network.Wai.Internal (Response (ResponseBuilder))
36+ responseLBS)
3837import Network.Wai.Test (defaultRequest, request,
3938 runSession, simpleBody,
4039 simpleHeaders, simpleStatus)
@@ -49,8 +48,9 @@ import Servant.API ((:<|>) (..), (:>), AuthProtect,
4948 Raw, RemoteHost, ReqBody,
5049 StdMethod (..), Verb, addHeader)
5150import Servant.API.Internal.Test.ComprehensiveAPI
52−import Servant.Server (ServantErr (..), Server, err401, err404,
53− serve, serveWithContext, Context((:.), EmptyContext))
51+import Servant.Server (Server, Handler, err401, err403,
52+ err404, serve, serveWithContext,
53+ Context((:.), EmptyContext))
5454import Test.Hspec (Spec, context, describe, it,
5555 shouldBe, shouldContain)
5656import qualified Test.Hspec.Wai as THW
@@ -63,11 +63,6 @@ import Servant.Server.Internal.BasicAuth (BasicAuthCheck(BasicAuthChec
6363import Servant.Server.Experimental.Auth
6464 (AuthHandler, AuthServerData,
6565 mkAuthHandler)
66−import Servant.Server.Internal.RoutingApplication
67− (toApplication, RouteResult(..))
68−import Servant.Server.Internal.Router
69− (tweakResponse, runRouter,
70− Router, Router'(LeafRouter))
7166import Servant.Server.Internal.Context
7267 (NamedContext(..))
7368
@@ -91,7 +86,6 @@ spec = do
9186 rawSpec
9287 alternativeSpec
9388 responseHeadersSpec
94− routerSpec
9589 miscCombinatorSpec
9690 basicAuthSpec
9791 genAuthSpec
@@ -105,6 +99,9 @@ type VerbApi method status
10599 :<|> "noContent" :> Verb method status '[JSON] NoContent
106100 :<|> "header" :> Verb method status '[JSON] (Headers '[Header "H" Int] Person)
107101 :<|> "headerNC" :> Verb method status '[JSON] (Headers '[Header "H" Int] NoContent)
102+ :<|> "accept" :> ( Verb method status '[JSON] Person
103+ :<|> Verb method status '[PlainText] String
104+ )
108105
109106verbSpec :: Spec
110107verbSpec = describe "Servant.API.Verb" $ do
@@ -113,6 +110,7 @@ verbSpec = describe "Servant.API.Verb" $ do
113110 :<|> return NoContent
114111 :<|> return (addHeader 5 alice)
115112 :<|> return (addHeader 10 NoContent)
113+ :<|> (return alice :<|> return "B")
116114 get200 = Proxy :: Proxy (VerbApi 'GET 200)
117115 post210 = Proxy :: Proxy (VerbApi 'POST 210)
118116 put203 = Proxy :: Proxy (VerbApi 'PUT 203)
@@ -167,6 +165,12 @@ verbSpec = describe "Servant.API.Verb" $ do
167165 [(hAccept, "application/json")] ""
168166 liftIO $ statusCode (simpleStatus response) `shouldBe` status
169167
168+ unless (status `elem` [214, 215] || method == methodHead) $
169+ it "allows modular specification of supported content types" $ do
170+ response <- THW.request method "/accept" [(hAccept, "text/plain")] ""
171+ liftIO $ statusCode (simpleStatus response) `shouldBe` status
172+ liftIO $ simpleBody response `shouldBe` "B"
173+
170174 it "sets the Content-Type header" $ do
171175 response <- THW.request method "" [] ""
172176 liftIO $ simpleHeaders response `shouldContain`
@@ -187,7 +191,7 @@ verbSpec = describe "Servant.API.Verb" $ do
187191type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal
188192captureApi :: Proxy CaptureApi
189193captureApi = Proxy
190−captureServer :: Integer -> ExceptT ServantErr IO Animal
194+captureServer :: Integer -> Handler Animal
191195captureServer legs = case legs of
192196 4 -> return jerry
193197 2 -> return tweety
@@ -343,11 +347,11 @@ headerApi = Proxy
343347headerSpec :: Spec
344348headerSpec = describe "Servant.API.Header" $ do
345349
346− let expectsInt :: Maybe Int -> ExceptT ServantErr IO ()
350+ let expectsInt :: Maybe Int -> Handler ()
347351 expectsInt (Just x) = when (x /= 5) $ error "Expected 5"
348352 expectsInt Nothing = error "Expected an int"
349353
350− let expectsString :: Maybe String -> ExceptT ServantErr IO ()
354+ let expectsString :: Maybe String -> Handler ()
351355 expectsString (Just x) = when (x /= "more from you") $ error "Expected more from you"
352356 expectsString Nothing = error "Expected a string"
353357
@@ -479,28 +483,6 @@ responseHeadersSpec = describe "ResponseHeaders" $ do
479483 THW.request method "" [(hAccept, "crazy/mime")] ""
480484 `shouldRespondWith` 406
481485
482−-- }}}
483−------------------------------------------------------------------------------
484−-- * routerSpec {{{
485−------------------------------------------------------------------------------
486−routerSpec :: Spec
487−routerSpec = do
488− describe "Servant.Server.Internal.Router" $ do
489− let app' :: Application
490− app' = toApplication $ runRouter router'
491−
492− router', router :: Router
493− router' = tweakResponse (twk <$>) router
494− router = LeafRouter $ \_ cont -> cont (Route $ responseBuilder (Status 201 "") [] "")
495−
496− twk :: Response -> Response
497− twk (ResponseBuilder (Status i s) hs b) = ResponseBuilder (Status (i + 1) s) hs b
498− twk b = b
499−
500− describe "tweakResponse" . with (return app') $ do
501− it "calls f on route result" $ do
502− get "" `shouldRespondWith` 202
503−
504486-- }}}
505487------------------------------------------------------------------------------
506488-- * miscCombinatorSpec {{{
@@ -542,20 +524,24 @@ miscCombinatorSpec = with (return $ serve miscApi miscServ) $
542524-- * Basic Authentication {{{
543525------------------------------------------------------------------------------
544526
545−type BasicAuthAPI = BasicAuth "foo" () :> "basic" :> Get '[JSON] Animal
527+type BasicAuthAPI =
528+ BasicAuth "foo" () :> "basic" :> Get '[JSON] Animal
529+ :<|> Raw
546530
547531basicAuthApi :: Proxy BasicAuthAPI
548532basicAuthApi = Proxy
533+
549534basicAuthServer :: Server BasicAuthAPI
550−basicAuthServer = const (return jerry)
535+basicAuthServer =
536+ const (return jerry) :<|>
537+ (\ _ respond -> respond $ responseLBS imATeaPot418 [] "")
551538
552539basicAuthContext :: Context '[ BasicAuthCheck () ]
553540basicAuthContext =
554− let basicHandler = BasicAuthCheck $ (\(BasicAuthData usr pass) ->
541+ let basicHandler = BasicAuthCheck $ \(BasicAuthData usr pass) ->
555542 if usr == "servant" && pass == "server"
556− then return (Authorized ())
557− else return Unauthorized
558− )
543+ then return (Authorized ())
544+ else return Unauthorized
559545 in basicHandler :. EmptyContext
560546
561547basicAuthSpec :: Spec
@@ -564,10 +550,21 @@ basicAuthSpec = do
564550 with (return (serveWithContext basicAuthApi basicAuthContext basicAuthServer)) $ do
565551
566552 context "Basic Authentication" $ do
567− it "returns with 401 with bad password" $ do
553+ let basicAuthHeaders user password =
554+ [("Authorization", "Basic " <> Base64.encode (user <> ":" <> password))]
555+ it "returns 401 when no credentials given" $ do
568556 get "/basic" `shouldRespondWith` 401
557+
558+ it "returns 403 when invalid credentials given" $ do
559+ THW.request methodGet "/basic" (basicAuthHeaders "servant" "wrong") ""
560+ `shouldRespondWith` 403
561+
569562 it "returns 200 with the right password" $ do
570− THW.request methodGet "/basic" [("Authorization","Basic c2VydmFudDpzZXJ2ZXI=")] "" `shouldRespondWith` 200
563+ THW.request methodGet "/basic" (basicAuthHeaders "servant" "server") ""
564+ `shouldRespondWith` 200
565+
566+ it "plays nice with subsequent Raw endpoints" $ do
567+ get "/foo" `shouldRespondWith` 418
571568
572569-- }}}
573570------------------------------------------------------------------------------
@@ -575,33 +572,43 @@ basicAuthSpec = do
575572------------------------------------------------------------------------------
576573
577574type GenAuthAPI = AuthProtect "auth" :> "auth" :> Get '[JSON] Animal
578−authApi :: Proxy GenAuthAPI
579−authApi = Proxy
580−authServer :: Server GenAuthAPI
581−authServer = const (return tweety)
575+ :<|> Raw
576+
577+genAuthApi :: Proxy GenAuthAPI
578+genAuthApi = Proxy
579+
580+genAuthServer :: Server GenAuthAPI
581+genAuthServer = const (return tweety)
582+ :<|> (\ _ respond -> respond $ responseLBS imATeaPot418 [] "")
582583
583584type instance AuthServerData (AuthProtect "auth") = ()
584585
585−genAuthContext :: Context '[ AuthHandler Request () ]
586+genAuthContext :: Context '[AuthHandler Request ()]
586587genAuthContext =
587− let authHandler = (\req ->
588− if elem ("Auth", "secret") (requestHeaders req)
589− then return ()
590− else throwE err401
591− )
588+ let authHandler = \req -> case lookup "Auth" (requestHeaders req) of
589+ Just "secret" -> return ()
590+ Just _ -> throwE err403
591+ Nothing -> throwE err401
592592 in mkAuthHandler authHandler :. EmptyContext
593593
594594genAuthSpec :: Spec
595595genAuthSpec = do
596596 describe "Servant.API.Auth" $ do
597− with (return (serveWithContext authApi genAuthContext authServer)) $ do
597+ with (return (serveWithContext genAuthApi genAuthContext genAuthServer)) $ do
598598
599599 context "Custom Auth Protection" $ do
600600 it "returns 401 when missing headers" $ do
601601 get "/auth" `shouldRespondWith` 401
602+
603+ it "returns 403 on wrong passwords" $ do
604+ THW.request methodGet "/auth" [("Auth","wrong")] "" `shouldRespondWith` 403
605+
602606 it "returns 200 with the right header" $ do
603607 THW.request methodGet "/auth" [("Auth","secret")] "" `shouldRespondWith` 200
604608
609+ it "plays nice with subsequent Raw endpoints" $ do
610+ get "/foo" `shouldRespondWith` 418
611+
605612-- }}}
606613------------------------------------------------------------------------------
607614-- * Test data types {{{
servant/CHANGELOG.md+7−1
@@ -1,5 +1,11 @@
1+0.7.1
2+-----
3+
4+* Add module `Servant.Utils.Enter` (https://github.com/haskell-servant/servant/pull/478)
5+* Allow to set the same header multiple times in responses.
6+
170.5
2−----
8+---
39
410* Add `WithNamedConfig` combinator.
511* Add `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
servant/servant.cabal+15−8
@@ -1,13 +1,13 @@
11name: servant
2−version: 0.6
2+version: 0.7.1
33synopsis: A family of combinators for defining webservices APIs
44description:
55 A family of combinators for defining webservices APIs and serving them
66 .
7− You can learn about the basics in the <http://haskell-servant.github.io/tutorial tutorial>.
7+ You can learn about the basics in the <http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html tutorial>.
88 .
99 <https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md CHANGELOG>
10−homepage: http://haskell-servant.github.io/
10+homepage: http://haskell-servant.readthedocs.org/
1111Bug-reports: http://github.com/haskell-servant/servant/issues
1212license: BSD3
1313license-file: LICENSE
@@ -16,9 +16,11 @@ maintainer: haskell-servant-maintainers@googlegroups.com
1616copyright: 2014-2016 Zalora South East Asia Pte Ltd, Servant Contributors
1717category: Web
1818build-type: Simple
19−extra-source-files: include/*.h
2019cabal-version: >=1.10
2120tested-with: GHC >= 7.8
21+extra-source-files:
22+ include/*.h
23+ CHANGELOG.md
2224source-repository head
2325 type: git
2426 location: http://github.com/haskell-servant/servant.git
@@ -45,8 +47,9 @@ library
4547 Servant.API.Verbs
4648 Servant.API.WithNamedContext
4749 Servant.Utils.Links
50+ Servant.Utils.Enter
4851 build-depends:
49− base >= 4.7 && < 4.9
52+ base >= 4.7 && < 4.10
5053 , base-compat >= 0.9
5154 , aeson >= 0.7
5255 , attoparsec >= 0.12
@@ -56,6 +59,8 @@ library
5659 , http-api-data >= 0.1 && < 0.3
5760 , http-media >= 0.4 && < 0.7
5861 , http-types >= 0.8 && < 0.10
62+ , mtl >= 2 && < 3
63+ , mmorph >= 1
5964 , text >= 1 && < 2
6065 , string-conversions >= 0.3 && < 0.5
6166 , network-uri >= 2.6
@@ -83,12 +88,13 @@ library
8388 , TypeSynonymInstances
8489 , UndecidableInstances
8590 ghc-options: -Wall
91+ if impl(ghc >= 8.0)
92+ ghc-options: -Wno-redundant-constraints
8693 include-dirs: include
8794
8895test-suite spec
8996 type: exitcode-stdio-1.0
90− ghc-options:
91− -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures
97+ ghc-options: -Wall
9298 default-language: Haskell2010
9399 hs-source-dirs: test
94100 main-is: Spec.hs
@@ -98,6 +104,7 @@ test-suite spec
98104 Servant.Utils.LinksSpec
99105 build-depends:
100106 base == 4.*
107+ , base-compat
101108 , aeson
102109 , attoparsec
103110 , bytestring
@@ -120,5 +127,5 @@ test-suite doctests
120127 main-is: test/Doctests.hs
121128 buildable: True
122129 default-language: Haskell2010
123− ghc-options: -threaded
130+ ghc-options: -Wall -threaded
124131 include-dirs: include
servant/src/Servant/API/Alternative.hs−2
@@ -1,9 +1,7 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE DeriveDataTypeable #-}
33{-# LANGUAGE DeriveFunctor #-}
4−#if !MIN_VERSION_base(4,8,0)
54{-# LANGUAGE DeriveFoldable #-}
6−#endif
75{-# LANGUAGE DeriveTraversable #-}
86{-# LANGUAGE TypeOperators #-}
97{-# OPTIONS_HADDOCK not-home #-}
servant/src/Servant/API/BasicAuth.hs+4−3
@@ -1,12 +1,13 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE KindSignatures #-}
4−{-# LANGUAGE PolyKinds #-}
3+{-# LANGUAGE KindSignatures #-}
4+{-# LANGUAGE PolyKinds #-}
5+
56module Servant.API.BasicAuth where
67
78import Data.ByteString (ByteString)
89import Data.Typeable (Typeable)
9−import GHC.TypeLits (Symbol)
10+import GHC.TypeLits (Symbol)
1011
1112
1213-- | Combinator for <https://tools.ietf.org/html/rfc2617#section-2 Basic Access Authentication>.
servant/src/Servant/API/ContentTypes.hs+2−2
@@ -154,7 +154,7 @@ newtype AcceptHeader = AcceptHeader BS.ByteString
154154-- > instance Accept MyContentType where
155155-- > contentType _ = "example" // "prs.me.mine" /: ("charset", "utf-8")
156156-- >
157−-- > instance Show a => MimeRender MyContentType where
157+-- > instance Show a => MimeRender MyContentType a where
158158-- > mimeRender _ val = pack ("This is MINE! " ++ show val)
159159-- >
160160-- > type MyAPI = "path" :> Get '[MyContentType] Int
@@ -169,7 +169,7 @@ class (AllMime list) => AllCTRender (list :: [*]) a where
169169 handleAcceptH :: Proxy list -> AcceptHeader -> a -> Maybe (ByteString, ByteString)
170170
171171instance OVERLAPPABLE_
172− (AllMimeRender (ct ': cts) a) => AllCTRender (ct ': cts) a where
172+ (Accept ct, AllMime cts, AllMimeRender (ct ': cts) a) => AllCTRender (ct ': cts) a where
173173 handleAcceptH _ (AcceptHeader accept) val = M.mapAcceptMedia lkup accept
174174 where pctyps = Proxy :: Proxy (ct ': cts)
175175 amrs = allMimeRender pctyps val
servant/src/Servant/API/Experimental/Auth.hs+2−3
@@ -1,7 +1,7 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE KindSignatures #-}
4−{-# LANGUAGE PolyKinds #-}
3+{-# LANGUAGE KindSignatures #-}
4+{-# LANGUAGE PolyKinds #-}
55module Servant.API.Experimental.Auth where
66
77import Data.Typeable (Typeable)
@@ -11,4 +11,3 @@ import Data.Typeable (Typeable)
1111--
1212-- NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE.
1313data AuthProtect (tag :: k) deriving (Typeable)
14−
servant/src/Servant/API/Header.hs+3−3
@@ -3,7 +3,9 @@
33{-# LANGUAGE DeriveFunctor #-}
44{-# LANGUAGE PolyKinds #-}
55{-# OPTIONS_HADDOCK not-home #-}
6−module Servant.API.Header where
6+module Servant.API.Header (
7+ Header(..),
8+) where
79
810import Data.ByteString (ByteString)
911import Data.Typeable (Typeable)
@@ -25,5 +27,3 @@ data Header (sym :: Symbol) a = Header a
2527-- >>> import Servant.API
2628-- >>> import Data.Aeson
2729-- >>> import Data.Text
28−-- >>> data Book
29−-- >>> instance ToJSON Book where { toJSON = undefined }
servant/src/Servant/API/ResponseHeaders.hs+5−11
@@ -68,8 +68,7 @@ class BuildHeadersTo hs where
6868instance OVERLAPPING_ BuildHeadersTo '[] where
6969 buildHeadersTo _ = HNil
7070
71−instance OVERLAPPABLE_ ( FromByteString v, BuildHeadersTo xs, KnownSymbol h
72− , Contains h xs ~ 'False)
71+instance OVERLAPPABLE_ ( FromByteString v, BuildHeadersTo xs, KnownSymbol h )
7372 => BuildHeadersTo ((Header h v) ': xs) where
7473 buildHeadersTo headers =
7574 let wantedHeader = CI.mk . pack $ symbolVal (Proxy :: Proxy h)
@@ -89,7 +88,7 @@ class GetHeaders ls where
8988instance OVERLAPPING_ GetHeaders (HList '[]) where
9089 getHeaders _ = []
9190
92−instance OVERLAPPABLE_ ( KnownSymbol h, ToByteString x, GetHeaders (HList xs))
91+instance OVERLAPPABLE_ ( KnownSymbol h, ToByteString x, GetHeaders (HList xs) )
9392 => GetHeaders (HList (Header h x ': xs)) where
9493 getHeaders hdrs = case hdrs of
9594 Header val `HCons` rest -> (headerName , toByteString' val):getHeaders rest
@@ -100,7 +99,7 @@ instance OVERLAPPABLE_ ( KnownSymbol h, ToByteString x, GetHeaders (HList xs))
10099instance OVERLAPPING_ GetHeaders (Headers '[] a) where
101100 getHeaders _ = []
102101
103−instance OVERLAPPABLE_ ( KnownSymbol h, GetHeaders (HList rest), ToByteString v)
102+instance OVERLAPPABLE_ ( KnownSymbol h, GetHeaders (HList rest), ToByteString v )
104103 => GetHeaders (Headers (Header h v ': rest) a) where
105104 getHeaders hs = getHeaders $ getHeadersHList hs
106105
@@ -112,20 +111,15 @@ class AddHeader h v orig new
112111 addHeader :: v -> orig -> new -- ^ N.B.: The same header can't be added multiple times
113112
114113
115−instance OVERLAPPING_ ( KnownSymbol h, ToByteString v, Contains h (fst ': rest) ~ 'False)
114+instance OVERLAPPING_ ( KnownSymbol h, ToByteString v )
116115 => AddHeader h v (Headers (fst ': rest) a) (Headers (Header h v ': fst ': rest) a) where
117116 addHeader a (Headers resp heads) = Headers resp (HCons (Header a) heads)
118117
119118instance OVERLAPPABLE_ ( KnownSymbol h, ToByteString v
120− , new ~ (Headers '[Header h v] a))
119+ , new ~ (Headers '[Header h v] a) )
121120 => AddHeader h v a new where
122121 addHeader a resp = Headers resp (HCons (Header a) HNil)
123122
124−type family Contains x xs where
125− Contains x ((Header x a) ': xs) = 'True
126− Contains x ((Header y a) ': xs) = Contains x xs
127− Contains x '[] = 'False
128−
129123-- $setup
130124-- >>> import Servant.API
131125-- >>> import Data.Aeson
servant/src/Servant/API/Vault.hs+2−2
@@ -9,8 +9,8 @@ import Data.Vault.Lazy (Vault)
99--
1010-- | Use 'Vault' in your API types to provide access to the 'Vault'
1111-- of the request, which is a location shared by middlewares and applications
12−-- to store arbitrary data. See 'Vault' for more details on how to actually
13−-- use the vault in your handlers
12+-- to store arbitrary data. See <https://hackage.haskell.org/package/vault vault>
13+-- for more details on how to actually use the vault in your handlers
1414--
1515-- Example:
1616--
servant/src/Servant/API/Verbs.hs+12−1
@@ -14,7 +14,9 @@ import GHC.Generics (Generic)
1414import GHC.TypeLits (Nat)
1515import Network.HTTP.Types.Method (Method, StdMethod (..),
1616 methodDelete, methodGet, methodHead,
17− methodPatch, methodPost, methodPut)
17+ methodPatch, methodPost, methodPut,
18+ methodTrace, methodConnect,
19+ methodOptions)
1820
1921-- | @Verb@ is a general type for representing HTTP verbs (a.k.a. methods). For
2022-- convenience, type synonyms for each verb with a 200 response code are
@@ -167,3 +169,12 @@ instance ReflectMethod 'PATCH where
167169
168170instance ReflectMethod 'HEAD where
169171 reflectMethod _ = methodHead
172+
173+instance ReflectMethod 'OPTIONS where
174+ reflectMethod _ = methodOptions
175+
176+instance ReflectMethod 'TRACE where
177+ reflectMethod _ = methodTrace
178+
179+instance ReflectMethod 'CONNECT where
180+ reflectMethod _ = methodConnect
servant-server/src/Servant/Server/Internal/Enter.hs → servant/src/Servant/Utils/Enter.hsrenamed+1−4
@@ -8,12 +8,9 @@
88{-# LANGUAGE TypeFamilies #-}
99{-# LANGUAGE TypeOperators #-}
1010{-# LANGUAGE UndecidableInstances #-}
11−module Servant.Server.Internal.Enter where
11+module Servant.Utils.Enter where
1212
1313import qualified Control.Category as C
14−#if MIN_VERSION_mtl(2,2,1)
15−import Control.Monad.Except
16−#endif
1714import Control.Monad.Identity
1815import Control.Monad.Morph
1916import Control.Monad.Reader
servant/src/Servant/Utils/Links.hs+12−8
@@ -72,14 +72,8 @@
7272-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] ())
7373-- >>> safeLink api bad_link
7474-- ...
75−-- Could not deduce (Or
76−-- (IsElem' (Verb 'DELETE 200 '[JSON] ()) (Verb 'GET 200 '[JSON] Int))
77−-- (IsElem'
78−-- ("hello" :> Delete '[JSON] ())
79−-- ("bye" :> (QueryParam "name" String :> Delete '[JSON] ()))))
80−-- arising from a use of ‘safeLink’
81−-- In the expression: safeLink api bad_link
82−-- In an equation for ‘it’: it = safeLink api bad_link
75+-- ...Could not deduce...
76+-- ...
8377--
8478-- This error is essentially saying that the type family couldn't find
8579-- bad_link under api after trying the open (but empty) type family
@@ -112,10 +106,12 @@ import Prelude ()
112106import Prelude.Compat
113107
114108import Web.HttpApiData
109+import Servant.API.BasicAuth ( BasicAuth )
115110import Servant.API.Capture ( Capture )
116111import Servant.API.ReqBody ( ReqBody )
117112import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
118113import Servant.API.Header ( Header )
114+import Servant.API.RemoteHost ( RemoteHost )
119115import Servant.API.Verbs ( Verb )
120116import Servant.API.Sub ( type (:>) )
121117import Servant.API.Raw ( Raw )
@@ -292,6 +288,14 @@ instance HasLink sub => HasLink (Header sym a :> sub) where
292288 type MkLink (Header sym a :> sub) = MkLink sub
293289 toLink _ = toLink (Proxy :: Proxy sub)
294290
291+instance HasLink sub => HasLink (RemoteHost :> sub) where
292+ type MkLink (RemoteHost :> sub) = MkLink sub
293+ toLink _ = toLink (Proxy :: Proxy sub)
294+
295+instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
296+ type MkLink (BasicAuth realm a :> sub) = MkLink sub
297+ toLink _ = toLink (Proxy :: Proxy sub)
298+
295299-- Verb (terminal) instances
296300instance HasLink (Verb m s ct a) where
297301 type MkLink (Verb m s ct a) = URI
servant/test/Servant/API/ContentTypesSpec.hs+5−5
@@ -3,14 +3,14 @@
33{-# LANGUAGE DeriveGeneric #-}
44{-# LANGUAGE MultiParamTypeClasses #-}
55{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE PackageImports #-}
67{-# LANGUAGE PolyKinds #-}
78{-# OPTIONS_GHC -fno-warn-orphans #-}
89module Servant.API.ContentTypesSpec where
910
10−#if !MIN_VERSION_base(4,8,0)
11−import Control.Applicative
12−import Data.Monoid
13−#endif
11+import Prelude ()
12+import Prelude.Compat
13+
1414import Control.Arrow
1515import Data.Aeson
1616import Data.ByteString.Char8 (ByteString, append, pack)
@@ -28,7 +28,7 @@ import GHC.Generics
2828import Network.URL (exportParams, importParams)
2929import Test.Hspec
3030import Test.QuickCheck
31−import Test.QuickCheck.Instances ()
31+import "quickcheck-instances" Test.QuickCheck.Instances ()
3232
3333import Servant.API.ContentTypes
3434
servant/test/Servant/Utils/LinksSpec.hs+5−5
@@ -67,27 +67,27 @@ spec = describe "Servant.Utils.Links" $ do
6767--
6868-- >>> apiLink (Proxy :: Proxy WrongPath)
6969-- ...
70−-- Could not deduce ...
70+-- ...Could not deduce...
7171-- ...
7272--
7373-- >>> apiLink (Proxy :: Proxy WrongReturnType)
7474-- ...
75−-- Could not deduce ...
75+-- ...Could not deduce...
7676-- ...
7777--
7878-- >>> apiLink (Proxy :: Proxy WrongContentType)
7979-- ...
80−-- Could not deduce ...
80+-- ...Could not deduce...
8181-- ...
8282--
8383-- >>> apiLink (Proxy :: Proxy WrongMethod)
8484-- ...
85−-- Could not deduce ...
85+-- ...Could not deduce...
8686-- ...
8787--
8888-- >>> apiLink (Proxy :: Proxy NotALink)
8989-- ...
90−-- Could not deduce ...
90+-- ...Could not deduce...
9191-- ...
9292--
9393-- sanity check
sources.txt+1−4
@@ -1,10 +1,7 @@
11servant
2−servant-cassava
2+servant-server
33servant-client
44servant-docs
55servant-foreign
66servant-js
7−servant-server
8−servant-blaze
9−servant-lucid
107servant-mock
stack-ghc-7.8.4.yaml+16−16
@@ -1,28 +1,28 @@
11flags: {}
22packages:
33- servant/
4−- servant-blaze/
5−- servant-cassava/
64- servant-client/
75- servant-docs/
8−- servant-examples/
96- servant-foreign/
107- servant-js/
11−- servant-lucid/
128- servant-mock/
139- servant-server/
1410extra-deps:
15−- base-compat-0.9.0
16−- hspec-2.2.0
17−- hspec-core-2.2.0
18−- hspec-discover-2.2.0
19−- hspec-expectations-0.7.2
20−- doctest-0.10.1
21−- engine-io-1.2.10
22−- engine-io-wai-1.0.3
23−- socket-io-1.3.3
24−- stm-delay-0.1.1.1
11+- base-compat-0.9.1
2512- control-monad-omega-0.3.1
26−- http-api-data-0.1.1.1
27−- should-not-typecheck-2.0.1
13+- cryptonite-0.6
14+- doctest-0.11.0
15+- hspec-2.2.3
16+- hspec-core-2.2.3
17+- hspec-discover-2.2.3
18+- hspec-expectations-0.7.2
19+- http-api-data-0.2.2
20+- primitive-0.6.1.0
21+- servant-0.7.1
22+- servant-client-0.7.1
23+- servant-docs-0.7.1
24+- servant-server-0.7.1
25+- should-not-typecheck-2.1.0
26+- time-locale-compat-0.1.1.1
27+- wai-app-static-3.1.5
2828resolver: lts-2.22
stack-ghc-8.0.1.yamlnew file+11
@@ -0,0 +1,11 @@
1+resolver: nightly-2016-05-27
2+packages:
3+- servant/
4+- servant-client/
5+- servant-docs/
6+- servant-foreign/
7+- servant-js/
8+- servant-mock/
9+- servant-server/
10+extra-deps: []
11+flags: {}
stack.yaml+2−14
@@ -1,24 +1,12 @@
1−flags:
2− servant-js:
3− example: false
1+flags: {}
42packages:
53- servant/
6−- servant-blaze/
7−- servant-cassava/
84- servant-client/
95- servant-docs/
106- servant-foreign/
117- servant-js/
12−- servant-lucid/
138- servant-mock/
149- servant-server/
1510- doc/tutorial
1611extra-deps:
17−- base-compat-0.9.0
18−- engine-io-wai-1.0.2
19−- control-monad-omega-0.3.1
20−- should-not-typecheck-2.0.1
21−- markdown-unlit-0.4.0
22−- aeson-0.11.0.0
23−- fail-4.9.0.0
24−resolver: nightly-2016-03-17
12+resolver: lts-6.0
travis.sh+1−1
@@ -6,7 +6,7 @@ for package in $(cat sources.txt) doc/tutorial ; do
66 echo testing $package
77 pushd $package
88 tinc
9− cabal configure --enable-tests --disable-optimization
9+ cabal configure --enable-tests --disable-optimization --ghc-options='-Werror'
1010 cabal build
1111 cabal test
1212 popd