on master help sign in
.github/workflows/master.yml+51−62
since splitsnapshots
unifiedsplit
@@ -13,12 +13,14 @@ jobs:
13 strategy:
13 strategy:
14 matrix:
14 matrix:
15 os: [ubuntu-latest]
15 os: [ubuntu-latest]
16− cabal: ["3.4"]
16+ cabal: ["3.6"]
17 ghc:
17 ghc:
18 - "8.6.5"
18 - "8.6.5"
19 - "8.8.4"
19 - "8.8.4"
20 - "8.10.7"
20 - "8.10.7"
21− - "9.0.1"
21+ - "9.0.2"
22+ - "9.2.2"
23+ - "9.4.2"
22
24
23 steps:
25 steps:
24 - uses: actions/checkout@v2
26 - uses: actions/checkout@v2
@@ -47,10 +49,7 @@ jobs:
47
49
48 - name: Configure
50 - name: Configure
49 run: |
51 run: |
50− # Using separate store-dir because default one already has 'ghc-paths' package installed
51− # with hardcoded path to ghcup's GHC path (which it was built with). This leads to failure in
52− # doctest, as it tries to invoke that GHC, and it doesn't exist here.
53− cabal --store-dir /tmp/cabal-store install --ignore-project -j2 doctest --constraint='doctest ^>=0.18'
52+ cabal install --ignore-project -j2 doctest --constraint='doctest ^>=0.20'
54
53
55 - name: Build
54 - name: Build
56 run: |
55 run: |
@@ -61,66 +60,56 @@ jobs:
61 cabal test all
60 cabal test all
62
61
63 - name: Run doctests
62 - name: Run doctests
64− # doctests are broken on GHC 9 due to compiler bug:
65− # https://gitlab.haskell.org/ghc/ghc/-/issues/19460
66− continue-on-error: ${{ matrix.ghc == '9.0.1' }}
67 run: |
63 run: |
68 # Necessary for doctest to be found in $PATH
64 # Necessary for doctest to be found in $PATH
69 export PATH="$HOME/.cabal/bin:$PATH"
65 export PATH="$HOME/.cabal/bin:$PATH"
70
66
71− # Filter out base-compat-batteries from .ghc.environment.*, as its modules
67+ DOCTEST="cabal repl --with-ghc=doctest --ghc-options=-w"
72− # conflict with those of base-compat.
73− #
74− # FIXME: This is an ugly hack. Ultimately, we'll want to use cabal-doctest
75− # (or cabal v2-doctest, if it ever lands) to provide a clean GHC environment.
76− # This might allow running doctests in GHCJS build as well.
77− perl -i -e 'while (<ARGV>) { print unless /package-id\s+(base-compat-batteries)-\d+(\.\d+)*/; }' .ghc.environment.*
78−
79− (cd servant && doctest src)
68+ (cd servant && eval $DOCTEST)
80− (cd servant-client && doctest src)
69+ (cd servant-client && eval $DOCTEST)
81− (cd servant-client-core && doctest src)
70+ (cd servant-client-core && eval $DOCTEST)
82− (cd servant-http-streams && doctest src)
71+ (cd servant-http-streams && eval $DOCTEST)
83− (cd servant-docs && doctest src)
72+ (cd servant-docs && eval $DOCTEST)
84− (cd servant-foreign && doctest src)
73+ (cd servant-foreign && eval $DOCTEST)
85− (cd servant-server && doctest src)
74+ (cd servant-server && eval $DOCTEST)
86− (cd servant-machines && doctest src)
75+ (cd servant-machines && eval $DOCTEST)
87− (cd servant-conduit && doctest src)
76+ (cd servant-conduit && eval $DOCTEST)
88− (cd servant-pipes && doctest src)
77+ (cd servant-pipes && eval $DOCTEST)
89−
78+
90− stack:
79+ # stack:
91− name: stack / ghc ${{ matrix.ghc }}
80+ # name: stack / ghc ${{ matrix.ghc }}
92− runs-on: ubuntu-latest
81+ # runs-on: ubuntu-latest
93− strategy:
82+ # strategy:
94− matrix:
83+ # matrix:
95− stack: ["2.7.3"]
84+ # stack: ["2.7.5"]
96− ghc: ["8.10.4"]
85+ # ghc: ["8.10.7"]
97−
86+
98− steps:
87+ # steps:
99− - uses: actions/checkout@v2
88+ # - uses: actions/checkout@v2
100−
89+
101− - uses: haskell/actions/setup@v1
90+ # - uses: haskell/actions/setup@v1
102− name: Setup Haskell Stack
91+ # name: Setup Haskell Stack
103− with:
92+ # with:
104− ghc-version: ${{ matrix.ghc }}
93+ # ghc-version: ${{ matrix.ghc }}
105− stack-version: ${{ matrix.stack }}
94+ # stack-version: ${{ matrix.stack }}
106−
95+
107− - uses: actions/cache@v2.1.3
96+ # - uses: actions/cache@v2.1.3
108− name: Cache ~/.stack
97+ # name: Cache ~/.stack
109− with:
98+ # with:
110− path: ~/.stack
99+ # path: ~/.stack
111− key: ${{ runner.os }}-${{ matrix.ghc }}-stack
100+ # key: ${{ runner.os }}-${{ matrix.ghc }}-stack
112−
101+
113− - name: Install dependencies
102+ # - name: Install dependencies
114− run: |
103+ # run: |
115− stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --only-dependencies
104+ # stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --only-dependencies
116−
105+
117− - name: Build
106+ # - name: Build
118− run: |
107+ # run: |
119− stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks
108+ # stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks
120−
109+
121− - name: Test
110+ # - name: Test
122− run: |
111+ # run: |
123− stack test --system-ghc
112+ # stack test --system-ghc
124
113
125 ghcjs:
114 ghcjs:
126 name: ubuntu-latest / ghcjs 8.6
115 name: ubuntu-latest / ghcjs 8.6
.gitignore+1
@@ -31,6 +31,7 @@ doc/venv
31doc/tutorial/static/api.js
31doc/tutorial/static/api.js
32doc/tutorial/static/jq.js
32doc/tutorial/static/jq.js
33shell.nix
33shell.nix
34+.hspec-failures
34
35
35# nix
36# nix
36result*
37result*
CONTRIBUTING.md+4−2
@@ -79,8 +79,10 @@ not been a timely response to a PR, you can ping the Maintainers group (with
79We encourage people to experiment with new combinators and instances - it is
79We encourage people to experiment with new combinators and instances - it is
80one of the most powerful ways of using `servant`, and a wonderful way of
80one of the most powerful ways of using `servant`, and a wonderful way of
81getting to know it better. If you do write a new combinator, we would love to
81getting to know it better. If you do write a new combinator, we would love to
82−know about it! Either hop on #servant on freenode and let us know, or open an
82+know about it! Either hop on
83+[#haskell-servant on libera.chat](https://web.libera.chat/#haskell-servant) and
83−issue with the `news` tag (which we will close when we read it).
84+let us know, or open an issue with the `news` tag (which we will close when we
85+read it).
84
86
85As for adding them to the main repo: maintaining combinators can be expensive,
87As for adding them to the main repo: maintaining combinators can be expensive,
86since official combinators must have instances for all classes (and new classes
88since official combinators must have instances for all classes (and new classes
cabal.project+1−23
@@ -47,30 +47,8 @@ packages:
47 doc/cookbook/using-custom-monad
47 doc/cookbook/using-custom-monad
48 doc/cookbook/using-free-client
48 doc/cookbook/using-free-client
49 -- doc/cookbook/open-id-connect
49 -- doc/cookbook/open-id-connect
50+ doc/cookbook/managed-resource
50
51
51tests: True
52tests: True
52optimization: False
53optimization: False
53-- reorder-goals: True
54-- reorder-goals: True
54−
55−constraints:
56− -- see https://github.com/haskell-infra/hackage-trustees/issues/119
57− foundation >=0.0.14,
58− memory <0.14.12 || >0.14.12
59−
60−constraints: base-compat ^>=0.11
61−constraints: semigroups ^>=0.19
62−
63−-- allow-newer: sqlite-simple-0.4.16.0:semigroups
64−-- allow-newer: direct-sqlite-2.3.24:semigroups
65−
66−-- needed for doctests
67−write-ghc-environment-files: always
68−
69−-- https://github.com/chordify/haskell-servant-pagination/pull/12
70−allow-newer: servant-pagination-2.2.2:servant
71−allow-newer: servant-pagination-2.2.2:servant-server
72−
73−allow-newer: servant-js:servant
74−
75−-- ghc 9
76−allow-newer: tdigest:base
changelog.d/1469new file+11
@@ -0,0 +1,11 @@
1+synopsis: Derive HasClient good response status from Verb status
2+prs: #1469
3+description: {
4+`HasClient` instances for the `Verb` datatype use `runRequest` in
5+`clientWithRoute` definitions.
6+This means that a request performed with `runClientM` will be successful if and
7+only if the endpoint specify a response status code >=200 and <300.
8+This change replaces `runRequest` with `runRequestAcceptStatus` in `Verb`
9+instances for the `HasClient` class, deriving the good response status from
10+the `Verb` status.
11+}
changelog.d/1529new file+10
@@ -0,0 +1,10 @@
1+synopsis: Fix performRequest in servant-client-ghcjs
2+prs: #1529
3+
4+description: {
5+
6+performRequest function in servant-client-ghcjs was not compatible with the
7+latest RunClient typeclass. Added the acceptStatus parameter and fixed the
8+functionality to match what servant-client provides.
9+
10+}
changelog.d/1556new file+81
@@ -0,0 +1,81 @@
1+synopsis: Display capture hints in router layout
2+prs: #1556
3+
4+description: {
5+
6+This PR enhances the `Servant.Server.layout` function, which produces a textual description of the routing layout of an API. More precisely, it changes `<capture>` blocks, so that they display the name and type of the variable being captured instead.
7+
8+Example:
9+
10+For the following API
11+```haskell
12+type API =
13+ "a" :> "d" :> Get '[JSON] NoContent
14+ :<|> "b" :> Capture "x" Int :> Get '[JSON] Bool
15+ :<|> "a" :> "e" :> Get '[JSON] Int
16+```
17+
18+we previously got the following output:
19+
20+```
21+/
22+├─ a/
23+│ ├─ d/
24+│ │ └─•
25+│ └─ e/
26+│ └─•
27+└─ b/
28+ └─ <capture>/
29+ ├─•
30+ ┆
31+ └─•
32+```
33+
34+now we get:
35+
36+```
37+/
38+├─ a/
39+│ ├─ d/
40+│ │ └─•
41+│ └─ e/
42+│ └─•
43+└─ b/
44+ └─ <x::Int>/
45+ ├─•
46+ ┆
47+ └─•
48+```
49+
50+This change is achieved by the introduction of a CaptureHint type, which is passed as an extra argument to the CaptureRouter and CaptureAllRouter constructors for the Router' type.
51+CaptureHint values are then used in routerLayout, to display the name and type of captured values, instead of just `<capture>` previously.
52+
53+N.B.:
54+Because the choice smart constructor for routers can aggregate Capture combinators with different capture hints, the Capture*Router constructors actually take a list of CaptureHint, instead of a single one.
55+
56+This PR also introduces Spec tests for the routerLayout function.
57+
58+Warning:
59+This change is potentially breaking, because it adds the constraint `Typeable a` to all types that are to be captured. Because all types are typeable since GHC 7.10, this is not as bad as it sounds ; it only break expressions where `a` is quantified in an expression with `Capture a`.
60+In those cases, the fix is easy: it suffices to add `Typeable a` to the left-hand side of the quantification constraint.
61+
62+For instance, the following code will no longer compile:
63+```haskell
64+type MyAPI a = Capture "foo" a :> Get '[JSON] ()
65+
66+myServer :: forall a. Server (MyAPI a)
67+myServer = const $ return ()
68+
69+myApi :: forall a. Proxy (MyAPI a)
70+myApi = Proxy
71+
72+app :: forall a. (FromHttpApiData a) => Application
73+app = serve (myApi @a) (myServer @a)
74+```
75+
76+Indeed, `app` should be replaced with:
77+```haskell
78+app :: forall a. (FromHttpApiData a, Typeable a) => Application
79+app = serve (myApi @a) (myServer @a)
80+```
81+}
changelog.d/1569new file+13
@@ -0,0 +1,13 @@
1+synopsis: Encode captures using toEncodedUrlPiece
2+prs: #1569
3+issues: #1511
4+
5+description: {
6+The `servant-client` library now makes direct use of `toEncodedUrlPiece` from `ToHttpApiData`
7+to encode captured values when building the request path. It gives user freedom to implement
8+URL-encoding however they need.
9+
10+Previous behavior was to use `toUrlPiece` and URL-encode its output using `toEncodedUrlPiece`
11+from the `Text` instance of `ToHttpApiData`. The issue with this approach is that
12+`ToHttpApiData Text` is overly zealous and also encodes characters, such as `*`, which are perfectly valid in a URL.
13+}
changelog.d/1571new file+10
@@ -0,0 +1,10 @@
1+synopsis: Support UVerb in servant-auth-server
2+prs: #1571
3+issues: #1570
4+description: {
5+UVerb endpoints are now supported by servant-auth-server and can be used under the
6+Auth combinator when writing servers. It is still unsupported by
7+servant-auth-client.
8+
9+This PR also brings Headers support in UVerb responses
10+}
changelog.d/1573new file+2
@@ -0,0 +1,2 @@
1+synopsis: Add API docs for ServerT
2+prs: #1573
changelog.d/1580new file+12
@@ -0,0 +1,12 @@
1+synopsis: Allow IO in validationKeys
2+prs: #1580
3+issues: #1579
4+
5+description: {
6+
7+Currently validationKeys are a fixed JWKSet. This does not work with OIDC
8+providers such as AWS Cognito or Okta, which regularly fetching jwks_uri to
9+discover new and expired keys.
10+
11+This change alters the type of validationKeys from JWKSet to IO JWKSet.
12+}
changelog.d/1589new file+2
@@ -0,0 +1,2 @@
1+synopsis: Only include question mark for nonempty query strings
2+prs: 1589
changelog.d/1595new file+2
@@ -0,0 +1,2 @@
1+synopsis: Run ClientEnv's makeClientRequest in IO.
2+prs: #1595
changelog.d/1606new file+10
@@ -0,0 +1,10 @@
1+synopsis: Handle Cookies correctly for RunStreamingClient
2+prs: #1606
3+issues: #1605
4+
5+description: {
6+
7+Makes performWithStreamingRequest take into consideration the
8+CookieJar, which it previously didn't.
9+
10+}
changelog.d/1638new file+2
@@ -0,0 +1,2 @@
1+synopsis: Add Functor instance to AuthHandler.
2+prs: #1638
changelog.d/1649new file+8
@@ -0,0 +1,8 @@
1+synopsis: Add HasStatus instance for Headers (that defers StatusOf to underlying value)
2+prs: #1649
3+
4+description: {
5+
6+Adds a new HasStatus (Headers hs a) instance (StatusOf (Headers hs a) = StatusOf a)
7+
8+}
doc/Makefile+1−1
@@ -10,7 +10,7 @@ BUILDDIR = _build
10
10
11# Put it first so that "make" without argument is like "make help".
11# Put it first so that "make" without argument is like "make help".
12help:
12help:
13− @if [ ! -d venv ]; then echo "WARNING: There is no venv directory, did you forget to 'virtualenv venv'. Check building-the-docs file."; fi
13+ @if [ ! -d venv ]; then echo "WARNING: There is no venv directory, did you forget to 'virtualenv venv'. Check README.md."; fi
14 @if [ ! "z$$(which $(SPHINXBUILD))" = "z$$(pwd)/venv/bin/sphinx-build" ]; then echo "WARNING: Did you forgot to 'source venv/bin/activate'"; fi
14 @if [ ! "z$$(which $(SPHINXBUILD))" = "z$$(pwd)/venv/bin/sphinx-build" ]; then echo "WARNING: Did you forgot to 'source venv/bin/activate'"; fi
15 @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
15 @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
16
16
doc/conf.py+1−2
@@ -46,7 +46,7 @@ master_doc = 'index'
46
46
47# General information about the project.
47# General information about the project.
48project = u'Servant'
48project = u'Servant'
49−copyright = u'2018, Servant Contributors'
49+copyright = u'2022, Servant Contributors'
50author = u'Servant Contributors'
50author = u'Servant Contributors'
51
51
52# The version info for the project you're documenting, acts as replacement for
52# The version info for the project you're documenting, acts as replacement for
@@ -169,4 +169,3 @@ texinfo_documents = [
169source_parsers = {
169source_parsers = {
170 '.lhs': CommonMarkParser,
170 '.lhs': CommonMarkParser,
171}
171}
172−
doc/cookbook/basic-auth/basic-auth.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-basic-auth
2name: cookbook-basic-auth
2version: 0.1
3version: 0.1
3synopsis: Basic Authentication cookbook example
4synopsis: Basic Authentication cookbook example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-basic-auth
13executable cookbook-basic-auth
doc/cookbook/basic-streaming/basic-streaming.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-basic-streaming
2name: cookbook-basic-streaming
2version: 2.1
3version: 2.1
3synopsis: Streaming in servant without streaming libs
4synopsis: Streaming in servant without streaming libs
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-basic-streaming
13executable cookbook-basic-streaming
doc/cookbook/curl-mock/curl-mock.cabal+4−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-curl-mock
2name: cookbook-curl-mock
2version: 0.1
3version: 0.1
3synopsis: Generate curl mock requests cookbook example
4synopsis: Generate curl mock requests cookbook example
@@ -7,10 +8,12 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbock-curl-mock
13executable cookbock-curl-mock
14+ if impl(ghc >= 9.2)
15+ -- generic-arbitrary is incompatible
16+ buildable: False
14 main-is: CurlMock.lhs
17 main-is: CurlMock.lhs
15 build-depends: base == 4.*
18 build-depends: base == 4.*
16 , aeson
19 , aeson
doc/cookbook/custom-errors/custom-errors.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-custom-errors
2name: cookbook-custom-errors
2version: 0.1
3version: 0.1
3synopsis: Return custom error messages from combinators
4synopsis: Return custom error messages from combinators
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-custom-errors
13executable cookbook-custom-errors
doc/cookbook/db-mysql-basics/mysql-basics.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: mysql-basics
2name: mysql-basics
2version: 0.1.0.0
3version: 0.1.0.0
3synopsis: Simple MySQL API cookbook example
4synopsis: Simple MySQL API cookbook example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11
11
12executable run
12executable run
13 hs-source-dirs: .
13 hs-source-dirs: .
doc/cookbook/db-postgres-pool/db-postgres-pool.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-db-postgres-pool
2name: cookbook-db-postgres-pool
2version: 0.1
3version: 0.1
3synopsis: Simple PostgreSQL connection pool cookbook example
4synopsis: Simple PostgreSQL connection pool cookbook example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-db-postgres-pool
13executable cookbook-db-postgres-pool
doc/cookbook/db-sqlite-simple/db-sqlite-simple.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-db-sqlite-simple
2name: cookbook-db-sqlite-simple
2version: 0.1
3version: 0.1
3synopsis: Simple SQLite DB cookbook example
4synopsis: Simple SQLite DB cookbook example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-db-sqlite-simple
13executable cookbook-db-sqlite-simple
doc/cookbook/file-upload/file-upload.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-file-upload
2name: cookbook-file-upload
2version: 0.1
3version: 0.1
3synopsis: File upload cookbook example
4synopsis: File upload cookbook example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-file-upload
13executable cookbook-file-upload
doc/cookbook/generic/generic.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-generic
2name: cookbook-generic
2version: 0.1
3version: 0.1
3synopsis: Using custom monad to pass a state between handlers
4synopsis: Using custom monad to pass a state between handlers
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-using-custom-monad
13executable cookbook-using-custom-monad
doc/cookbook/hoist-server-with-context/hoist-server-with-context.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-hoist-server-with-context
2name: cookbook-hoist-server-with-context
2version: 0.0.1
3version: 0.0.1
3synopsis: JWT and basic access authentication with a Custom Monad cookbook example
4synopsis: JWT and basic access authentication with a Custom Monad cookbook example
@@ -10,7 +11,6 @@ author: Servant Contributors
10maintainer: haskell-servant-maintainers@googlegroups.com
11maintainer: haskell-servant-maintainers@googlegroups.com
11category: Servant
12category: Servant
12build-type: Simple
13build-type: Simple
13−cabal-version: >=1.10
14tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
14tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
15
15
16executable cookbook-hoist-server-with-context
16executable cookbook-hoist-server-with-context
doc/cookbook/https/https.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-https
2name: cookbook-https
2version: 0.1
3version: 0.1
3synopsis: HTTPS cookbook example
4synopsis: HTTPS cookbook example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-https
13executable cookbook-https
doc/cookbook/index.rst+3−2
@@ -6,8 +6,8 @@ how to solve many common problems with servant. If you're
6interested in contributing examples of your own, feel free
6interested in contributing examples of your own, feel free
7to open an issue or a pull request on
7to open an issue or a pull request on
8`our github repository <https://github.com/haskell-servant/servant>`_
8`our github repository <https://github.com/haskell-servant/servant>`_
9−or even to just get in touch with us on the **#servant** IRC channel
9+or even to just get in touch with us on the `**#haskell-servant** IRC channel
10−on freenode or on
10+on libera.chat <https://web.libera.chat/#haskell-servant>_ or on
11`the mailing list <https://groups.google.com/forum/#!forum/haskell-servant>`_.
11`the mailing list <https://groups.google.com/forum/#!forum/haskell-servant>`_.
12
12
13The scope is very wide. Simple and fancy authentication schemes,
13The scope is very wide. Simple and fancy authentication schemes,
@@ -37,3 +37,4 @@ you name it!
37 sentry/Sentry.lhs
37 sentry/Sentry.lhs
38 testing/Testing.lhs
38 testing/Testing.lhs
39 open-id-connect/OpenIdConnect.lhs
39 open-id-connect/OpenIdConnect.lhs
40+ managed-resource/ManagedResource.lhs
doc/cookbook/jwt-and-basic-auth/jwt-and-basic-auth.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-jwt-and-basic-auth
2name: cookbook-jwt-and-basic-auth
2version: 0.0.1
3version: 0.0.1
3synopsis: JWT and basic access authentication cookbook example
4synopsis: JWT and basic access authentication cookbook example
@@ -10,7 +11,6 @@ author: Servant Contributors
10maintainer: haskell-servant-maintainers@googlegroups.com
11maintainer: haskell-servant-maintainers@googlegroups.com
11category: Servant
12category: Servant
12build-type: Simple
13build-type: Simple
13−cabal-version: >=1.10
14tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
14tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
15
15
16executable cookbook-jwt-and-basic-auth
16executable cookbook-jwt-and-basic-auth
doc/cookbook/managed-resource/ManagedResource.lhsnew file+114
@@ -0,0 +1,114 @@
1+# Request-lifetime Managed Resources
2+
3+Let's see how we can write a handle that uses a resource managed by Servant. The resource is created automatically by Servant when the server recieves a request, and the resource is automatically destroyed when the server is finished handling a request.
4+
5+As usual, we start with a little bit of throat clearing.
6+
7+
8+``` haskell
9+{-# LANGUAGE DataKinds #-}
10+{-# LANGUAGE TypeOperators #-}
11+import Control.Concurrent
12+import Control.Exception (bracket, throwIO)
13+import Control.Monad.IO.Class
14+import Control.Monad.Trans.Resource
15+import Data.Acquire
16+import Network.HTTP.Client (newManager, defaultManagerSettings)
17+import Network.Wai.Handler.Warp
18+import Servant
19+import Servant.Client
20+import System.IO
21+```
22+
23+Here we define an API type that uses the `WithResource` combinator. The server handler for an endpoint with a `WithResource res` component will receive a value of that type as an argument.
24+
25+``` haskell
26+type API = WithResource Handle :> ReqBody '[PlainText] String :> Post '[JSON] NoContent
27+
28+api :: Proxy API
29+api = Proxy
30+```
31+
32+But this resource value has to come from somewhere. Servant obtains the value using an Acquire provided in the context. The Acquire knows how to both create and destroy resources of a particular type.
33+
34+``` haskell
35+appContext :: Context '[Acquire Handle]
36+appContext = acquireHandle :. EmptyContext
37+
38+acquireHandle :: Acquire Handle
39+acquireHandle = mkAcquire newHandle closeHandle
40+
41+newHandle :: IO Handle
42+newHandle = do
43+ putStrLn "opening file"
44+ h <- openFile "test.txt" AppendMode
45+ putStrLn "opened file"
46+ return h
47+
48+closeHandle :: Handle -> IO ()
49+closeHandle h = do
50+ putStrLn "closing file"
51+ hClose h
52+ putStrLn "closed file"
53+```
54+
55+Now we create the handler which will use this resource. This handler will write the request message to the System.IO.Handle which was provided to us. In some situations the handler will succeed, but in some in will fail. In either case, Servant will clean up the resource for us.
56+
57+``` haskell
58+server :: Server API
59+server = writeToFile
60+
61+ where writeToFile :: (ReleaseKey, Handle) -> String -> Handler NoContent
62+ writeToFile (_, h) msg = case msg of
63+ "illegal" -> error "wait, that's illegal!"
64+ legalMsg -> liftIO $ do
65+ putStrLn "writing file"
66+ hPutStrLn h legalMsg
67+ putStrLn "wrote file"
68+ return NoContent
69+```
70+
71+Finally we run the server in the background while we post messages to it.
72+
73+``` haskell
74+runApp :: IO ()
75+runApp = run 8080 (serveWithContext api appContext $ server)
76+
77+postMsg :: String -> ClientM NoContent
78+postMsg = client api
79+
80+main :: IO ()
81+main = do
82+ mgr <- newManager defaultManagerSettings
83+ bracket (forkIO $ runApp) killThread $ \_ -> do
84+ ms <- flip runClientM (mkClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do
85+ liftIO $ putStrLn "sending hello message"
86+ _ <- postMsg "hello"
87+ liftIO $ putStrLn "sending illegal message"
88+ _ <- postMsg "illegal"
89+ liftIO $ putStrLn "done"
90+ print ms
91+```
92+
93+This program prints
94+
95+```
96+sending hello message
97+opening file
98+opened file
99+writing file
100+wrote file
101+closing file
102+closed file
103+sending illegal message
104+opening file
105+opened file
106+closing file
107+closed file
108+wait, that's illegal!
109+CallStack (from HasCallStack):
110+ error, called at ManagedResource.lhs:63:24 in main:Main
111+Left (FailureResponse (Request {requestPath = (BaseUrl {baseUrlScheme = Http, baseUrlHost = "localhost", baseUrlPort = 8080, baseUrlPath = ""},""), requestQueryString = fromList [], requestBody = Just ((),text/plain;charset=utf-8), requestAccept = fromList [], requestHeaders = fromList [], requestHttpVersion = HTTP/1.1, requestMethod = "POST"}) (Response {responseStatusCode = Status {statusCode = 500, statusMessage = "Internal Server Error"}, responseHeaders = fromList [("Transfer-Encoding","chunked"),("Date","Thu, 24 Nov 2022 21:04:47 GMT"),("Server","Warp/3.3.23"),("Content-Type","text/plain; charset=utf-8")], responseHttpVersion = HTTP/1.1, responseBody = "Something went wrong"}))
112+```
113+
114+and appends to a file called `test.txt`. We can see from the output that when a legal message is sent, the file is opened, written to, and closed. We can also see that when an illegal message is sent, the file is opened but not written to. Crucially, it is still closed even though the handler threw an exception.
doc/cookbook/managed-resource/managed-resource.cabalnew file+30
@@ -0,0 +1,30 @@
1+cabal-version: 2.2
2+name: cookbook-managed-resource
3+version: 0.1
4+synopsis: Simple managed resource cookbook example
5+homepage: http://docs.servant.dev/
6+license: BSD-3-Clause
7+license-file: ../../../servant/LICENSE
8+author: Servant Contributors
9+maintainer: haskell-servant-maintainers@googlegroups.com
10+build-type: Simple
11+tested-with: GHC==9.4.2
12+
13+executable cookbook-managed-resource
14+ main-is: ManagedResource.lhs
15+ build-depends: base == 4.*
16+ , text >= 1.2
17+ , aeson >= 1.2
18+ , servant
19+ , servant-client
20+ , servant-server
21+ , warp >= 3.2
22+ , wai >= 3.2
23+ , http-types >= 0.12
24+ , markdown-unlit >= 0.4
25+ , http-client >= 0.5
26+ , transformers
27+ , resourcet
28+ default-language: Haskell2010
29+ ghc-options: -Wall -pgmL markdown-unlit
30+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/open-id-connect/OpenIdConnect.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: open-id-connect
2name: open-id-connect
2version: 0.1
3version: 0.1
3synopsis: OpenId Connect with Servant example
4synopsis: OpenId Connect with Servant example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >= 1.10
11tested-with: GHC==8.6.5
11tested-with: GHC==8.6.5
12
12
13executable cookbook-openidconnect
13executable cookbook-openidconnect
doc/cookbook/open-id-connect/OpenIdConnect.lhs+1−1
@@ -330,7 +330,7 @@ data Customer = Customer {
330```
330```
331
331
332Here is the code that displays the homepage.
332Here is the code that displays the homepage.
333−It should contain a link to the the `/login` URL.
333+It should contain a link to the `/login` URL.
334When the user clicks on this link it will be redirected to Google login page
334When the user clicks on this link it will be redirected to Google login page
335with some generated information.
335with some generated information.
336
336
doc/cookbook/pagination/pagination.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-pagination
2name: cookbook-pagination
2version: 2.1
3version: 2.1
3synopsis: Pagination with Servant example
4synopsis: Pagination with Servant example
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-pagination
13executable cookbook-pagination
doc/cookbook/sentry/sentry.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-sentry
2name: cookbook-sentry
2version: 0.1
3version: 0.1
3synopsis: Collecting runtime exceptions using Sentry
4synopsis: Collecting runtime exceptions using Sentry
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-sentry
13executable cookbook-sentry
doc/cookbook/structuring-apis/structuring-apis.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-structuring-apis
2name: cookbook-structuring-apis
2version: 0.1
3version: 0.1
3synopsis: Example that shows how APIs can be structured
4synopsis: Example that shows how APIs can be structured
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-structuring-apis
13executable cookbook-structuring-apis
doc/cookbook/testing/testing.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-testing
2name: cookbook-testing
2version: 0.0.1
3version: 0.0.1
3synopsis: Common testing patterns in Servant apps
4synopsis: Common testing patterns in Servant apps
@@ -9,7 +10,6 @@ author: Servant Contributors
9maintainer: haskell-servant-maintainers@googlegroups.com
10maintainer: haskell-servant-maintainers@googlegroups.com
10category: Servant
11category: Servant
11build-type: Simple
12build-type: Simple
12−cabal-version: >=1.10
13tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
13tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
14
14
15executable cookbook-testing
15executable cookbook-testing
doc/cookbook/using-custom-monad/using-custom-monad.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-using-custom-monad
2name: cookbook-using-custom-monad
2version: 0.1
3version: 0.1
3synopsis: Using custom monad to pass a state between handlers
4synopsis: Using custom monad to pass a state between handlers
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-using-custom-monad
13executable cookbook-using-custom-monad
doc/cookbook/using-free-client/UsingFreeClient.lhs+1−1
@@ -119,7 +119,7 @@ Now we can use `servant-client`'s internals to convert servant's `Request`
119to http-client's `Request`, and we can inspect it:
119to http-client's `Request`, and we can inspect it:
120
120
121```haskell
121```haskell
122− let req' = I.defaultMakeClientRequest burl req
122+ req' <- I.defaultMakeClientRequest burl req
123 putStrLn $ "Making request: " ++ show req'
123 putStrLn $ "Making request: " ++ show req'
124```
124```
125
125
doc/cookbook/using-free-client/using-free-client.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-using-free-client
2name: cookbook-using-free-client
2version: 0.1
3version: 0.1
3synopsis: Using Free client
4synopsis: Using Free client
@@ -7,7 +8,6 @@ license-file: ../../../servant/LICENSE
7author: Servant Contributors
8author: Servant Contributors
8maintainer: haskell-servant-maintainers@googlegroups.com
9maintainer: haskell-servant-maintainers@googlegroups.com
9build-type: Simple
10build-type: Simple
10−cabal-version: >=1.10
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
11tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
12
12
13executable cookbook-using-free-client
13executable cookbook-using-free-client
doc/cookbook/uverb/UVerb.lhs+1−1
@@ -199,7 +199,7 @@ parsers in the hope that the ones that should will always error out so
199you can try until the right one returns a value.)
199you can try until the right one returns a value.)
200
200
201[servant-exceptions](https://github.com/ch1bo/servant-exceptions) is
201[servant-exceptions](https://github.com/ch1bo/servant-exceptions) is
202−another shot at at the problem. It is inspired by
202+another shot at the problem. It is inspired by
203servant-checked-exceptions, so it may be worth taking a closer look.
203servant-checked-exceptions, so it may be worth taking a closer look.
204The README claims that
204The README claims that
205[cardano-sl](https://github.com/input-output-hk/cardano-sl) also has
205[cardano-sl](https://github.com/input-output-hk/cardano-sl) also has
doc/cookbook/uverb/uverb.cabal+1−1
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: cookbook-uverb
2name: cookbook-uverb
2version: 0.0.1
3version: 0.0.1
3synopsis: How to use the 'UVerb' type.
4synopsis: How to use the 'UVerb' type.
@@ -9,7 +10,6 @@ author: Servant Contributors
9maintainer: haskell-servant-maintainers@googlegroups.com
10maintainer: haskell-servant-maintainers@googlegroups.com
10category: Servant
11category: Servant
11build-type: Simple
12build-type: Simple
12−cabal-version: >=1.10
13tested-with: GHC==8.6.5, GHC==8.8.4, GHC==8.10.7
13tested-with: GHC==8.6.5, GHC==8.8.4, GHC==8.10.7
14
14
15executable cookbook-uverb
15executable cookbook-uverb
doc/links.rst+1−1
@@ -12,7 +12,7 @@ Helpful Links
12 `https://github.com/haskell-servant/servant/issues <https://github.com/haskell-servant/servant/issues>`_
12 `https://github.com/haskell-servant/servant/issues <https://github.com/haskell-servant/servant/issues>`_
13
13
14- the irc channel:
14- the irc channel:
15− ``#servant`` on freenode
15+ `#haskell-servant on libera.chat <https://web.libera.chat/#haskell-servant>`_
16
16
17- the mailing list:
17- the mailing list:
18 `groups.google.com/forum/#!forum/haskell-servant <https://groups.google.com/forum/#!forum/haskell-servant>`_
18 `groups.google.com/forum/#!forum/haskell-servant <https://groups.google.com/forum/#!forum/haskell-servant>`_
doc/requirements.txt+1
@@ -1,3 +1,4 @@
1recommonmark==0.5.0
1recommonmark==0.5.0
2Sphinx==1.8.4
2Sphinx==1.8.4
3sphinx_rtd_theme>=0.4.2
3sphinx_rtd_theme>=0.4.2
4+jinja2<3.1.0
doc/tutorial/install.rst+26
@@ -40,3 +40,29 @@ nix
40`Nix <https://nixos.org/nix/>`_ users should feel free to take a look at
40`Nix <https://nixos.org/nix/>`_ users should feel free to take a look at
41the `nix/shell.nix` file in the repository and use it to provision a suitable
41the `nix/shell.nix` file in the repository and use it to provision a suitable
42environment to build and run the examples.
42environment to build and run the examples.
43+
44+Note for Ubuntu users
45+--------
46+
47+Ubuntu's packages for `ghc`, `cabal`, and `stack` are years out of date.
48+If the instructions above fail for you,
49+try replacing the Ubuntu packages with up-to-date versions.
50+First remove the installed versions:
51+
52+.. code-block:: bash
53+
54+ # remove the obsolete versions
55+ $ sudo apt remove ghc haskell-stack cabal-install
56+
57+Then install fresh versions of the Haskell toolchain
58+using the `ghcup <https://www.haskell.org/ghcup/install/>`_ installer.
59+
60+As of February 2022, one easy way to do this is by running a bootstrap script:
61+
62+.. code-block:: bash
63+
64+ $ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
65+
66+The script is interactive and will prompt you for details about what
67+you want installed and where. To install manually,
68+see `the detailed instructions <https://www.haskell.org/ghcup/install/#manual-install>`_.
doc/tutorial/tutorial.cabal+2−2
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: tutorial
2name: tutorial
2version: 0.10
3version: 0.10
3synopsis: The servant tutorial
4synopsis: The servant tutorial
@@ -11,7 +12,6 @@ license-file: LICENSE
11author: Servant Contributors
12author: Servant Contributors
12maintainer: haskell-servant-maintainers@googlegroups.com
13maintainer: haskell-servant-maintainers@googlegroups.com
13build-type: Simple
14build-type: Simple
14−cabal-version: >=1.10
15tested-with:
15tested-with:
16 GHC==8.6.5
16 GHC==8.6.5
17 GHC==8.8.3, GHC ==8.10.7
17 GHC==8.8.3, GHC ==8.10.7
@@ -64,7 +64,7 @@ library
64 , blaze-markup >= 0.8.0.0 && < 0.9
64 , blaze-markup >= 0.8.0.0 && < 0.9
65 , cookie >= 0.4.3 && < 0.5
65 , cookie >= 0.4.3 && < 0.5
66 , js-jquery >= 3.3.1 && < 3.4
66 , js-jquery >= 3.3.1 && < 3.4
67− , lucid >= 2.9.11 && < 2.10
67+ , lucid >= 2.9.11 && < 2.12
68 , random >= 1.1 && < 1.3
68 , random >= 1.1 && < 1.3
69 , servant-js >= 0.9 && < 0.10
69 , servant-js >= 0.9 && < 0.10
70 , time >= 1.6.0.1 && < 1.13
70 , time >= 1.6.0.1 && < 1.13
servant-auth/servant-auth-client/servant-auth-client.cabal+12−12
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: servant-auth-client
2name: servant-auth-client
2version: 0.4.1.0
3version: 0.4.1.0
3synopsis: servant-client/servant-auth compatibility
4synopsis: servant-client/servant-auth compatibility
@@ -6,9 +7,9 @@ description: This package provides instances that allow generating clients fr
6 APIs that use
7 APIs that use
7 <https://hackage.haskell.org/package/servant-auth servant-auth's> @Auth@ combinator.
8 <https://hackage.haskell.org/package/servant-auth servant-auth's> @Auth@ combinator.
8 .
9 .
9− For a quick overview of the usage, see the <http://github.com/haskell-servant/servant/servant-auth#readme README>.
10+ For a quick overview of the usage, see the <https://github.com/haskell-servant/servant/tree/master/servant-auth#readme README>.
10category: Web, Servant, Authentication
11category: Web, Servant, Authentication
11−homepage: http://github.com/haskell-servant/servant/servant-auth#readme
12+homepage: https://github.com/haskell-servant/servant/tree/master/servant-auth#readme
12bug-reports: https://github.com/haskell-servant/servant/issues
13bug-reports: https://github.com/haskell-servant/servant/issues
13author: Julian K. Arni
14author: Julian K. Arni
14maintainer: jkarni@gmail.com
15maintainer: jkarni@gmail.com
@@ -17,7 +18,6 @@ license: BSD-3-Clause
17license-file: LICENSE
18license-file: LICENSE
18tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
19tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
19build-type: Simple
20build-type: Simple
20−cabal-version: >= 1.10
21extra-source-files:
21extra-source-files:
22 CHANGELOG.md
22 CHANGELOG.md
23
23
@@ -31,12 +31,12 @@ library
31 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
31 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
32 ghc-options: -Wall
32 ghc-options: -Wall
33 build-depends:
33 build-depends:
34− base >= 4.10 && < 4.16
34+ base >= 4.10 && < 4.18
35− , bytestring >= 0.10.6.0 && < 0.11
35+ , bytestring >= 0.10.6.0 && < 0.12
36 , containers >= 0.5.6.2 && < 0.7
36 , containers >= 0.5.6.2 && < 0.7
37 , servant-auth == 0.4.*
37 , servant-auth == 0.4.*
38− , servant >= 0.13 && < 0.19
38+ , servant >= 0.13 && < 0.20
39− , servant-client-core >= 0.13 && < 0.19
39+ , servant-client-core >= 0.13 && < 0.20
40
40
41 exposed-modules:
41 exposed-modules:
42 Servant.Auth.Client
42 Servant.Auth.Client
@@ -50,7 +50,7 @@ test-suite spec
50 test
50 test
51 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
51 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
52 ghc-options: -Wall
52 ghc-options: -Wall
53− build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.9
53+ build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.10
54
54
55 -- dependencies with bounds inherited from the library stanza
55 -- dependencies with bounds inherited from the library stanza
56 build-depends:
56 build-depends:
@@ -62,19 +62,19 @@ test-suite spec
62
62
63 -- test dependencies
63 -- test dependencies
64 build-depends:
64 build-depends:
65− hspec >= 2.5.5 && < 2.9
65+ hspec >= 2.5.5 && < 2.10
66 , QuickCheck >= 2.11.3 && < 2.15
66 , QuickCheck >= 2.11.3 && < 2.15
67 , aeson >= 1.3.1.1 && < 3
67 , aeson >= 1.3.1.1 && < 3
68− , bytestring >= 0.10.6.0 && < 0.11
68+ , bytestring >= 0.10.6.0 && < 0.12
69 , http-client >= 0.5.13.1 && < 0.8
69 , http-client >= 0.5.13.1 && < 0.8
70 , http-types >= 0.12.2 && < 0.13
70 , http-types >= 0.12.2 && < 0.13
71 , servant-auth-server >= 0.4.2.0 && < 0.5
71 , servant-auth-server >= 0.4.2.0 && < 0.5
72− , servant-server >= 0.13 && < 0.19
72+ , servant-server >= 0.13 && < 0.20
73 , time >= 1.5.0.1 && < 1.13
73 , time >= 1.5.0.1 && < 1.13
74 , transformers >= 0.4.2.0 && < 0.6
74 , transformers >= 0.4.2.0 && < 0.6
75 , wai >= 3.2.1.2 && < 3.3
75 , wai >= 3.2.1.2 && < 3.3
76 , warp >= 3.2.25 && < 3.4
76 , warp >= 3.2.25 && < 3.4
77− , jose >= 0.7.0.0 && < 0.10
77+ , jose >= 0.10 && < 0.11
78 other-modules:
78 other-modules:
79 Servant.Auth.ClientSpec
79 Servant.Auth.ClientSpec
80 default-language: Haskell2010
80 default-language: Haskell2010
servant-auth/servant-auth-docs/servant-auth-docs.cabal+10−10
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: servant-auth-docs
2name: servant-auth-docs
2version: 0.2.10.0
3version: 0.2.10.0
3synopsis: servant-docs/servant-auth compatibility
4synopsis: servant-docs/servant-auth compatibility
@@ -6,9 +7,9 @@ description: This package provides instances that allow generating docs from
6 APIs that use
7 APIs that use
7 <https://hackage.haskell.org/package/servant-auth servant-auth's> @Auth@ combinator.
8 <https://hackage.haskell.org/package/servant-auth servant-auth's> @Auth@ combinator.
8 .
9 .
9− For a quick overview of the usage, see the <http://github.com/haskell-servant/servant/servant-auth#readme README>.
10+ For a quick overview of the usage, see the <https://github.com/haskell-servant/servant/tree/master/servant-auth#readme README>.
10category: Web, Servant, Authentication
11category: Web, Servant, Authentication
11−homepage: http://github.com/haskell-servant/servant/servant-auth#readme
12+homepage: https://github.com/haskell-servant/servant/tree/master/servant-auth#readme
12bug-reports: https://github.com/haskell-servant/servant/issues
13bug-reports: https://github.com/haskell-servant/servant/issues
13author: Julian K. Arni
14author: Julian K. Arni
14maintainer: jkarni@gmail.com
15maintainer: jkarni@gmail.com
@@ -17,7 +18,6 @@ license: BSD-3-Clause
17license-file: LICENSE
18license-file: LICENSE
18tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
19tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
19build-type: Custom
20build-type: Custom
20−cabal-version: >= 1.10
21extra-source-files:
21extra-source-files:
22 CHANGELOG.md
22 CHANGELOG.md
23
23
@@ -35,11 +35,11 @@ library
35 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
35 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
36 ghc-options: -Wall
36 ghc-options: -Wall
37 build-depends:
37 build-depends:
38− base >= 4.10 && < 4.16
38+ base >= 4.10 && < 4.18
39− , servant-docs >= 0.11.2 && < 0.12
39+ , servant-docs >= 0.11.2 && < 0.13
40− , servant >= 0.13 && < 0.19
40+ , servant >= 0.13 && < 0.20
41 , servant-auth == 0.4.*
41 , servant-auth == 0.4.*
42− , lens >= 4.16.1 && <5.1
42+ , lens >= 4.16.1 && <5.3
43 exposed-modules:
43 exposed-modules:
44 Servant.Auth.Docs
44 Servant.Auth.Docs
45 default-language: Haskell2010
45 default-language: Haskell2010
@@ -50,7 +50,7 @@ test-suite doctests
50 build-depends:
50 build-depends:
51 base,
51 base,
52 servant-auth-docs,
52 servant-auth-docs,
53− doctest >= 0.16 && < 0.19,
53+ doctest >= 0.16 && < 0.21,
54 QuickCheck >= 2.11.3 && < 2.15,
54 QuickCheck >= 2.11.3 && < 2.15,
55 template-haskell
55 template-haskell
56 ghc-options: -Wall -threaded
56 ghc-options: -Wall -threaded
@@ -64,7 +64,7 @@ test-suite spec
64 test
64 test
65 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
65 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
66 ghc-options: -Wall
66 ghc-options: -Wall
67− build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.9
67+ build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.10
68
68
69 -- dependencies with bounds inherited from the library stanza
69 -- dependencies with bounds inherited from the library stanza
70 build-depends:
70 build-depends:
@@ -78,7 +78,7 @@ test-suite spec
78 -- test dependencies
78 -- test dependencies
79 build-depends:
79 build-depends:
80 servant-auth-docs
80 servant-auth-docs
81− , hspec >= 2.5.5 && < 2.9
81+ , hspec >= 2.5.5 && < 2.10
82 , QuickCheck >= 2.11.3 && < 2.15
82 , QuickCheck >= 2.11.3 && < 2.15
83
83
84 default-language: Haskell2010
84 default-language: Haskell2010
servant-auth/servant-auth-server/servant-auth-server.cabal+18−17
@@ -1,14 +1,15 @@
1+cabal-version: 2.2
1name: servant-auth-server
2name: servant-auth-server
2−version: 0.4.6.0
3+version: 0.4.7.0
3synopsis: servant-server/servant-auth compatibility
4synopsis: servant-server/servant-auth compatibility
4description: This package provides the required instances for using the @Auth@ combinator
5description: This package provides the required instances for using the @Auth@ combinator
5 in your 'servant' server.
6 in your 'servant' server.
6 .
7 .
7 Both cookie- and token- (REST API) based authentication is provided.
8 Both cookie- and token- (REST API) based authentication is provided.
8 .
9 .
9− For a quick overview of the usage, see the <http://github.com/haskell-servant/servant/servant-auth#readme README>.
10+ For a quick overview of the usage, see the <https://github.com/haskell-servant/servant/tree/master/servant-auth#readme README>.
10category: Web, Servant, Authentication
11category: Web, Servant, Authentication
11−homepage: http://github.com/haskell-servant/servant/servant-auth#readme
12+homepage: https://github.com/haskell-servant/servant/tree/master/servant-auth#readme
12bug-reports: https://github.com/haskell-servant/servant/issues
13bug-reports: https://github.com/haskell-servant/servant/issues
13author: Julian K. Arni
14author: Julian K. Arni
14maintainer: jkarni@gmail.com
15maintainer: jkarni@gmail.com
@@ -17,7 +18,6 @@ license: BSD-3-Clause
17license-file: LICENSE
18license-file: LICENSE
18tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
19tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
19build-type: Simple
20build-type: Simple
20−cabal-version: >= 1.10
21extra-source-files:
21extra-source-files:
22 CHANGELOG.md
22 CHANGELOG.md
23
23
@@ -31,27 +31,27 @@ library
31 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
31 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
32 ghc-options: -Wall
32 ghc-options: -Wall
33 build-depends:
33 build-depends:
34− base >= 4.10 && < 4.16
34+ base >= 4.10 && < 4.18
35 , aeson >= 1.0.0.1 && < 3
35 , aeson >= 1.0.0.1 && < 3
36 , base64-bytestring >= 1.0.0.1 && < 2
36 , base64-bytestring >= 1.0.0.1 && < 2
37 , blaze-builder >= 0.4.1.0 && < 0.5
37 , blaze-builder >= 0.4.1.0 && < 0.5
38− , bytestring >= 0.10.6.0 && < 0.11
38+ , bytestring >= 0.10.6.0 && < 0.12
39 , case-insensitive >= 1.2.0.11 && < 1.3
39 , case-insensitive >= 1.2.0.11 && < 1.3
40 , cookie >= 0.4.4 && < 0.5
40 , cookie >= 0.4.4 && < 0.5
41 , data-default-class >= 0.1.2.0 && < 0.2
41 , data-default-class >= 0.1.2.0 && < 0.2
42 , entropy >= 0.4.1.3 && < 0.5
42 , entropy >= 0.4.1.3 && < 0.5
43 , http-types >= 0.12.2 && < 0.13
43 , http-types >= 0.12.2 && < 0.13
44− , jose >= 0.7.0.0 && < 0.10
44+ , jose >= 0.10 && < 0.11
45− , lens >= 4.16.1 && < 5.1
45+ , lens >= 4.16.1 && < 5.3
46− , memory >= 0.14.16 && < 0.17
46+ , memory >= 0.14.16 && < 0.19
47 , monad-time >= 0.3.1.0 && < 0.4
47 , monad-time >= 0.3.1.0 && < 0.4
48− , mtl >= 2.2.2 && < 2.3
48+ , mtl ^>= 2.2.2 || ^>= 2.3.1
49− , servant >= 0.13 && < 0.19
49+ , servant >= 0.13 && < 0.20
50 , servant-auth == 0.4.*
50 , servant-auth == 0.4.*
51− , servant-server >= 0.13 && < 0.19
51+ , servant-server >= 0.13 && < 0.20
52 , tagged >= 0.8.4 && < 0.9
52 , tagged >= 0.8.4 && < 0.9
53− , text >= 1.2.3.0 && < 1.3
53+ , text >= 1.2.3.0 && < 2.1
54− , time >= 1.5.0.1 && < 1.10
54+ , time >= 1.5.0.1 && < 1.13
55 , unordered-containers >= 0.2.9.0 && < 0.3
55 , unordered-containers >= 0.2.9.0 && < 0.3
56 , wai >= 3.2.1.2 && < 3.3
56 , wai >= 3.2.1.2 && < 3.3
57
57
@@ -102,7 +102,7 @@ test-suite spec
102 test
102 test
103 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
103 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
104 ghc-options: -Wall
104 ghc-options: -Wall
105− build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.8
105+ build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.10
106
106
107 -- dependencies with bounds inherited from the library stanza
107 -- dependencies with bounds inherited from the library stanza
108 build-depends:
108 build-depends:
@@ -123,12 +123,13 @@ test-suite spec
123 -- test dependencies
123 -- test dependencies
124 build-depends:
124 build-depends:
125 servant-auth-server
125 servant-auth-server
126− , hspec >= 2.5.5 && < 2.8
126+ , hspec >= 2.5.5 && < 2.10
127 , QuickCheck >= 2.11.3 && < 2.15
127 , QuickCheck >= 2.11.3 && < 2.15
128 , http-client >= 0.5.13.1 && < 0.8
128 , http-client >= 0.5.13.1 && < 0.8
129− , lens-aeson >= 1.0.2 && < 1.2
129+ , lens-aeson >= 1.0.2 && < 1.3
130 , warp >= 3.2.25 && < 3.4
130 , warp >= 3.2.25 && < 3.4
131 , wreq >= 0.5.2.1 && < 0.6
131 , wreq >= 0.5.2.1 && < 0.6
132+ , text >= 1.2.3.0 && < 2.1
132 other-modules:
133 other-modules:
133 Servant.Auth.ServerSpec
134 Servant.Auth.ServerSpec
134 default-language: Haskell2010
135 default-language: Haskell2010
servant-auth/servant-auth-server/src/Servant/Auth/Server/Internal.hs+6−9
@@ -54,15 +54,12 @@ instance ( n ~ 'S ('S 'Z)
54
54
55 makeCookies :: AuthResult v -> IO (SetCookieList ('S ('S 'Z)))
55 makeCookies :: AuthResult v -> IO (SetCookieList ('S ('S 'Z)))
56 makeCookies authResult = do
56 makeCookies authResult = do
57− xsrf <- makeXsrfCookie cookieSettings
58− fmap (Just xsrf `SetCookieCons`) $
59− case authResult of
57+ case authResult of
60− (Authenticated v) -> do
58+ (Authenticated v) -> do
61− ejwt <- makeSessionCookie cookieSettings jwtSettings v
59+ ejwt <- makeSessionCookie cookieSettings jwtSettings v
62− case ejwt of
60+ xsrf <- makeXsrfCookie cookieSettings
63− Nothing -> return $ Nothing `SetCookieCons` SetCookieNil
64− Just jwt -> return $ Just jwt `SetCookieCons` SetCookieNil
61+ return $ Just xsrf `SetCookieCons` (ejwt `SetCookieCons` SetCookieNil)
65− _ -> return $ Nothing `SetCookieCons` SetCookieNil
62+ _ -> return $ Nothing `SetCookieCons` (Nothing `SetCookieCons` SetCookieNil)
66
63
67 go :: (AuthResult v -> ServerT api Handler)
64 go :: (AuthResult v -> ServerT api Handler)
68 -> (AuthResult v, SetCookieList n)
65 -> (AuthResult v, SetCookieList n)
servant-auth/servant-auth-server/src/Servant/Auth/Server/Internal/AddSetCookie.hs+26−1
@@ -11,6 +11,9 @@ import Data.Tagged (Tagged (..))
11import qualified Network.HTTP.Types as HTTP
11import qualified Network.HTTP.Types as HTTP
12import Network.Wai (mapResponseHeaders)
12import Network.Wai (mapResponseHeaders)
13import Servant
13import Servant
14+import Servant.API.UVerb.Union
15+import Servant.API.Generic
16+import Servant.Server.Generic
14import Web.Cookie
17import Web.Cookie
15
18
16-- What are we doing here? Well, the idea is to add headers to the response,
19-- What are we doing here? Well, the idea is to add headers to the response,
@@ -31,11 +34,24 @@ type family AddSetCookieApiVerb a where
31 AddSetCookieApiVerb (Headers ls a) = Headers (Header "Set-Cookie" SetCookie ': ls) a
34 AddSetCookieApiVerb (Headers ls a) = Headers (Header "Set-Cookie" SetCookie ': ls) a
32 AddSetCookieApiVerb a = Headers '[Header "Set-Cookie" SetCookie] a
35 AddSetCookieApiVerb a = Headers '[Header "Set-Cookie" SetCookie] a
33
36
37+#if MIN_VERSION_servant_server(0,18,1)
38+type family MapAddSetCookieApiVerb (as :: [*]) where
39+ MapAddSetCookieApiVerb '[] = '[]
40+ MapAddSetCookieApiVerb (a ': as) = (AddSetCookieApiVerb a ': MapAddSetCookieApiVerb as)
41+#endif
42+
34type family AddSetCookieApi a :: *
43type family AddSetCookieApi a :: *
35type instance AddSetCookieApi (a :> b) = a :> AddSetCookieApi b
44type instance AddSetCookieApi (a :> b) = a :> AddSetCookieApi b
36type instance AddSetCookieApi (a :<|> b) = AddSetCookieApi a :<|> AddSetCookieApi b
45type instance AddSetCookieApi (a :<|> b) = AddSetCookieApi a :<|> AddSetCookieApi b
46+#if MIN_VERSION_servant_server(0,19,0)
47+type instance AddSetCookieApi (NamedRoutes api) = AddSetCookieApi (ToServantApi api)
48+#endif
37type instance AddSetCookieApi (Verb method stat ctyps a)
49type instance AddSetCookieApi (Verb method stat ctyps a)
38 = Verb method stat ctyps (AddSetCookieApiVerb a)
50 = Verb method stat ctyps (AddSetCookieApiVerb a)
51+#if MIN_VERSION_servant_server(0,18,1)
52+type instance AddSetCookieApi (UVerb method ctyps as)
53+ = UVerb method ctyps (MapAddSetCookieApiVerb as)
54+#endif
39type instance AddSetCookieApi Raw = Raw
55type instance AddSetCookieApi Raw = Raw
40#if MIN_VERSION_servant_server(0,15,0)
56#if MIN_VERSION_servant_server(0,15,0)
41type instance AddSetCookieApi (Stream method stat framing ctyps a)
57type instance AddSetCookieApi (Stream method stat framing ctyps a)
@@ -54,7 +70,7 @@ instance {-# OVERLAPS #-} AddSetCookies ('S n) oldb newb
54 => AddSetCookies ('S n) (a -> oldb) (a -> newb) where
70 => AddSetCookies ('S n) (a -> oldb) (a -> newb) where
55 addSetCookies cookies oldfn = addSetCookies cookies . oldfn
71 addSetCookies cookies oldfn = addSetCookies cookies . oldfn
56
72
57−instance AddSetCookies 'Z orig orig where
73+instance (orig1 ~ orig2) => AddSetCookies 'Z orig1 orig2 where
58 addSetCookies _ = id
74 addSetCookies _ = id
59
75
60instance {-# OVERLAPPABLE #-}
76instance {-# OVERLAPPABLE #-}
@@ -72,6 +88,15 @@ instance {-# OVERLAPS #-}
72 => AddSetCookies ('S n) (a :<|> b) (a' :<|> b') where
88 => AddSetCookies ('S n) (a :<|> b) (a' :<|> b') where
73 addSetCookies cookies (a :<|> b) = addSetCookies cookies a :<|> addSetCookies cookies b
89 addSetCookies cookies (a :<|> b) = addSetCookies cookies a :<|> addSetCookies cookies b
74
90
91+instance {-# OVERLAPS #-}
92+ ( AddSetCookies ('S n) (ServerT (ToServantApi api) m) cookiedApi
93+ , Generic (api (AsServerT m))
94+ , GServantProduct (Rep (api (AsServerT m)))
95+ , ToServant api (AsServerT m) ~ ServerT (ToServantApi api) m
96+ )
97+ => AddSetCookies ('S n) (api (AsServerT m)) cookiedApi where
98+ addSetCookies cookies = addSetCookies cookies . toServant
99+
75-- | for @servant <0.11@
100-- | for @servant <0.11@
76instance
101instance
77 AddSetCookies ('S n) Application Application where
102 AddSetCookies ('S n) Application Application where
servant-auth/servant-auth-server/src/Servant/Auth/Server/Internal/ConfigTypes.hs+2−2
@@ -33,7 +33,7 @@ data JWTSettings = JWTSettings
33 -- | Algorithm used to sign JWT.
33 -- | Algorithm used to sign JWT.
34 , jwtAlg :: Maybe Jose.Alg
34 , jwtAlg :: Maybe Jose.Alg
35 -- | Keys used to validate JWT.
35 -- | Keys used to validate JWT.
36− , validationKeys :: Jose.JWKSet
36+ , validationKeys :: IO Jose.JWKSet
37 -- | An @aud@ predicate. The @aud@ is a string or URI that identifies the
37 -- | An @aud@ predicate. The @aud@ is a string or URI that identifies the
38 -- intended recipient of the JWT.
38 -- intended recipient of the JWT.
39 , audienceMatches :: Jose.StringOrURI -> IsMatch
39 , audienceMatches :: Jose.StringOrURI -> IsMatch
@@ -44,7 +44,7 @@ defaultJWTSettings :: Jose.JWK -> JWTSettings
44defaultJWTSettings k = JWTSettings
44defaultJWTSettings k = JWTSettings
45 { signingKey = k
45 { signingKey = k
46 , jwtAlg = Nothing
46 , jwtAlg = Nothing
47− , validationKeys = Jose.JWKSet [k]
47+ , validationKeys = pure $ Jose.JWKSet [k]
48 , audienceMatches = const Matches }
48 , audienceMatches = const Matches }
49
49
50-- | The policies to use when generating cookies.
50-- | The policies to use when generating cookies.
servant-auth/servant-auth-server/src/Servant/Auth/Server/Internal/Cookie.hs+1
@@ -2,6 +2,7 @@
2module Servant.Auth.Server.Internal.Cookie where
2module Servant.Auth.Server.Internal.Cookie where
3
3
4import Blaze.ByteString.Builder (toByteString)
4import Blaze.ByteString.Builder (toByteString)
5+import Control.Monad (MonadPlus(..), guard)
5import Control.Monad.Except
6import Control.Monad.Except
6import Control.Monad.Reader
7import Control.Monad.Reader
7import qualified Crypto.JOSE as Jose
8import qualified Crypto.JOSE as Jose
servant-auth/servant-auth-server/src/Servant/Auth/Server/Internal/JWT.hs+6−9
@@ -1,18 +1,14 @@
1module Servant.Auth.Server.Internal.JWT where
1module Servant.Auth.Server.Internal.JWT where
2
2
3import Control.Lens
3import Control.Lens
4−import Control.Monad.Except
4+import Control.Monad (MonadPlus(..), guard)
5import Control.Monad.Reader
5import Control.Monad.Reader
6import qualified Crypto.JOSE as Jose
6import qualified Crypto.JOSE as Jose
7import qualified Crypto.JWT as Jose
7import qualified Crypto.JWT as Jose
8−import Data.Aeson (FromJSON, Result (..), ToJSON, fromJSON,
9− toJSON)
10import Data.ByteArray (constEq)
8import Data.ByteArray (constEq)
11import qualified Data.ByteString as BS
9import qualified Data.ByteString as BS
12import qualified Data.ByteString.Lazy as BSL
10import qualified Data.ByteString.Lazy as BSL
13−import qualified Data.HashMap.Strict as HM
14import Data.Maybe (fromMaybe)
11import Data.Maybe (fromMaybe)
15−import qualified Data.Text as T
16import Data.Time (UTCTime)
12import Data.Time (UTCTime)
17import Network.Wai (requestHeaders)
13import Network.Wai (requestHeaders)
18
14
@@ -42,7 +38,7 @@ jwtAuthCheck jwtSettings = do
42-- token expires.
38-- token expires.
43makeJWT :: ToJWT a
39makeJWT :: ToJWT a
44 => a -> JWTSettings -> Maybe UTCTime -> IO (Either Jose.Error BSL.ByteString)
40 => a -> JWTSettings -> Maybe UTCTime -> IO (Either Jose.Error BSL.ByteString)
45−makeJWT v cfg expiry = runExceptT $ do
41+makeJWT v cfg expiry = Jose.runJOSE $ do
46 bestAlg <- Jose.bestJWSAlg $ signingKey cfg
42 bestAlg <- Jose.bestJWSAlg $ signingKey cfg
47 let alg = fromMaybe bestAlg $ jwtAlg cfg
43 let alg = fromMaybe bestAlg $ jwtAlg cfg
48 ejwt <- Jose.signClaims (signingKey cfg)
44 ejwt <- Jose.signClaims (signingKey cfg)
@@ -58,14 +54,15 @@ makeJWT v cfg expiry = runExceptT $ do
58
54
59verifyJWT :: FromJWT a => JWTSettings -> BS.ByteString -> IO (Maybe a)
55verifyJWT :: FromJWT a => JWTSettings -> BS.ByteString -> IO (Maybe a)
60verifyJWT jwtCfg input = do
56verifyJWT jwtCfg input = do
57+ keys <- validationKeys jwtCfg
61− verifiedJWT <- liftIO $ runExceptT $ do
58+ verifiedJWT <- Jose.runJOSE $ do
62 unverifiedJWT <- Jose.decodeCompact (BSL.fromStrict input)
59 unverifiedJWT <- Jose.decodeCompact (BSL.fromStrict input)
63 Jose.verifyClaims
60 Jose.verifyClaims
64 (jwtSettingsToJwtValidationSettings jwtCfg)
61 (jwtSettingsToJwtValidationSettings jwtCfg)
65− (validationKeys jwtCfg)
62+ keys
66 unverifiedJWT
63 unverifiedJWT
67 return $ case verifiedJWT of
64 return $ case verifiedJWT of
68 Left (_ :: Jose.JWTError) -> Nothing
65 Left (_ :: Jose.JWTError) -> Nothing
69 Right v -> case decodeJWT v of
66 Right v -> case decodeJWT v of
70 Left _ -> Nothing
67 Left _ -> Nothing
71− Right v' -> Just v'
68+ Right v' -> Just v'
servant-auth/servant-auth-server/src/Servant/Auth/Server/Internal/ThrowAll.hs+10−1
@@ -8,7 +8,10 @@ module Servant.Auth.Server.Internal.ThrowAll where
8
8
9import Control.Monad.Error.Class
9import Control.Monad.Error.Class
10import Data.Tagged (Tagged (..))
10import Data.Tagged (Tagged (..))
11−import Servant ((:<|>) (..), ServerError(..))
11+import Servant ((:<|>) (..), ServerError(..), NamedRoutes(..))
12+import Servant.API.Generic
13+import Servant.Server.Generic
14+import Servant.Server
12import Network.HTTP.Types
15import Network.HTTP.Types
13import Network.Wai
16import Network.Wai
14
17
@@ -26,6 +29,12 @@ class ThrowAll a where
26instance (ThrowAll a, ThrowAll b) => ThrowAll (a :<|> b) where
29instance (ThrowAll a, ThrowAll b) => ThrowAll (a :<|> b) where
27 throwAll e = throwAll e :<|> throwAll e
30 throwAll e = throwAll e :<|> throwAll e
28
31
32+instance
33+ ( ThrowAll (ToServant api (AsServerT m)) , GenericServant api (AsServerT m)) =>
34+ ThrowAll (api (AsServerT m)) where
35+
36+ throwAll = fromServant . throwAll
37+
29-- Really this shouldn't be necessary - ((->) a) should be an instance of
38-- Really this shouldn't be necessary - ((->) a) should be an instance of
30-- MonadError, no?
39-- MonadError, no?
31instance {-# OVERLAPPING #-} ThrowAll b => ThrowAll (a -> b) where
40instance {-# OVERLAPPING #-} ThrowAll b => ThrowAll (a -> b) where
servant-auth/servant-auth-server/src/Servant/Auth/Server/Internal/Types.hs+1
@@ -2,6 +2,7 @@
2module Servant.Auth.Server.Internal.Types where
2module Servant.Auth.Server.Internal.Types where
3
3
4import Control.Applicative
4import Control.Applicative
5+import Control.Monad (MonadPlus(..), ap)
5import Control.Monad.Reader
6import Control.Monad.Reader
6import Control.Monad.Time
7import Control.Monad.Time
7import Data.Monoid (Monoid (..))
8import Data.Monoid (Monoid (..))
servant-auth/servant-auth-server/test/Servant/Auth/ServerSpec.hs+39−5
@@ -1,4 +1,5 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2+{-# LANGUAGE TypeApplications #-}
2module Servant.Auth.ServerSpec (spec) where
3module Servant.Auth.ServerSpec (spec) where
3
4
4#if !MIN_VERSION_servant_server(0,16,0)
5#if !MIN_VERSION_servant_server(0,16,0)
@@ -6,13 +7,12 @@ module Servant.Auth.ServerSpec (spec) where
6#endif
7#endif
7
8
8import Control.Lens
9import Control.Lens
9−import Control.Monad.Except (runExceptT)
10import Control.Monad.IO.Class (liftIO)
10import Control.Monad.IO.Class (liftIO)
11import Crypto.JOSE (Alg (HS256, None), Error,
11import Crypto.JOSE (Alg (HS256, None), Error,
12 JWK, JWSHeader,
12 JWK, JWSHeader,
13 KeyMaterialGenParam (OctGenParam),
13 KeyMaterialGenParam (OctGenParam),
14 ToCompact, encodeCompact,
14 ToCompact, encodeCompact,
15− genJWK, newJWSHeader)
15+ genJWK, newJWSHeader, runJOSE)
16import Crypto.JWT (Audience (..), ClaimsSet,
16import Crypto.JWT (Audience (..), ClaimsSet,
17 NumericDate (NumericDate),
17 NumericDate (NumericDate),
18 SignedJWT,
18 SignedJWT,
@@ -25,6 +25,7 @@ import Data.Aeson (FromJSON, ToJSON, Value,
25import Data.Aeson.Lens (_JSON)
25import Data.Aeson.Lens (_JSON)
26import qualified Data.ByteString as BS
26import qualified Data.ByteString as BS
27import qualified Data.ByteString.Lazy as BSL
27import qualified Data.ByteString.Lazy as BSL
28+import Data.Text (Text, pack)
28import Data.CaseInsensitive (mk)
29import Data.CaseInsensitive (mk)
29import Data.Foldable (find)
30import Data.Foldable (find)
30import Data.Monoid
31import Data.Monoid
@@ -40,6 +41,7 @@ import Network.HTTP.Types (Status, status200,
40import Network.Wai (responseLBS)
41import Network.Wai (responseLBS)
41import Network.Wai.Handler.Warp (testWithApplication)
42import Network.Wai.Handler.Warp (testWithApplication)
42import Network.Wreq (Options, auth, basicAuth,
43import Network.Wreq (Options, auth, basicAuth,
44+ checkResponse,
43 cookieExpiryTime, cookies,
45 cookieExpiryTime, cookies,
44 defaults, get, getWith, postWith,
46 defaults, get, getWith, postWith,
45 header, oauth2Bearer,
47 header, oauth2Bearer,
@@ -50,6 +52,7 @@ import Network.Wreq (Options, auth, basicAuth,
50import Network.Wreq.Types (Postable(..))
52import Network.Wreq.Types (Postable(..))
51import Servant hiding (BasicAuth,
53import Servant hiding (BasicAuth,
52 IsSecure (..), header)
54 IsSecure (..), header)
55+import Servant.API.Generic ((:-))
53import Servant.Auth.Server
56import Servant.Auth.Server
54import Servant.Auth.Server.Internal.Cookie (expireTime)
57import Servant.Auth.Server.Internal.Cookie (expireTime)
55import Servant.Auth.Server.SetCookieOrphan ()
58import Servant.Auth.Server.SetCookieOrphan ()
@@ -182,8 +185,21 @@ cookieAuthSpec
182 it "fails with no XSRF header or cookie" $ \port -> property
185 it "fails with no XSRF header or cookie" $ \port -> property
183 $ \(user :: User) -> do
186 $ \(user :: User) -> do
184 jwt <- createJWT theKey (newJWSHeader ((), HS256)) (claims $ toJSON user)
187 jwt <- createJWT theKey (newJWSHeader ((), HS256)) (claims $ toJSON user)
185− opts <- addJwtToCookie cookieCfg jwt
188+ opts' <- addJwtToCookie cookieCfg jwt
189+ let opts = opts' & checkResponse .~ Just mempty
186− getWith opts (url port) `shouldHTTPErrorWith` status401
190+ resp <- getWith opts (url port)
191+ resp ^. responseStatus `shouldBe` status401
192+ (resp ^. responseCookieJar) `shouldNotHaveCookies` ["XSRF-TOKEN"]
193+
194+ -- Validating that the XSRF cookie isn't added for UVerb routes either.
195+ -- These routes can return a 401 response directly without using throwError / throwAll,
196+ -- which revealed a bug:
197+ --
198+ -- https://github.com/haskell-servant/servant/issues/1570#issuecomment-1076374449
199+ resp <- getWith opts (url port ++ "/uverb")
200+ resp ^. responseStatus `shouldBe` status401
201+ (resp ^. responseCookieJar) `shouldNotHaveCookies` ["XSRF-TOKEN"]
202+
187
203
188 it "succeeds if XSRF header and cookie match, and JWT is valid" $ \port -> property
204 it "succeeds if XSRF header and cookie match, and JWT is valid" $ \port -> property
189 $ \(user :: User) -> do
205 $ \(user :: User) -> do
@@ -405,17 +421,23 @@ type API auths
405 = Auth auths User :>
421 = Auth auths User :>
406 ( Get '[JSON] Int
422 ( Get '[JSON] Int
407 :<|> ReqBody '[JSON] Int :> Post '[JSON] Int
423 :<|> ReqBody '[JSON] Int :> Post '[JSON] Int
424+ :<|> "named" :> NamedRoutes DummyRoutes
408 :<|> "header" :> Get '[JSON] (Headers '[Header "Blah" Int] Int)
425 :<|> "header" :> Get '[JSON] (Headers '[Header "Blah" Int] Int)
409#if MIN_VERSION_servant_server(0,15,0)
426#if MIN_VERSION_servant_server(0,15,0)
410 :<|> "stream" :> StreamGet NoFraming OctetStream (SourceIO BS.ByteString)
427 :<|> "stream" :> StreamGet NoFraming OctetStream (SourceIO BS.ByteString)
411#endif
428#endif
412 :<|> "raw" :> Raw
429 :<|> "raw" :> Raw
413 )
430 )
431+ :<|> "uverb" :> Auth auths User :> UVerb 'GET '[JSON] '[WithStatus 200 Int, WithStatus 401 Text, WithStatus 403 Text]
414 :<|> "login" :> ReqBody '[JSON] User :> Post '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
432 :<|> "login" :> ReqBody '[JSON] User :> Post '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
415 , Header "Set-Cookie" SetCookie ] NoContent)
433 , Header "Set-Cookie" SetCookie ] NoContent)
416 :<|> "logout" :> Get '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
434 :<|> "logout" :> Get '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
417 , Header "Set-Cookie" SetCookie ] NoContent)
435 , Header "Set-Cookie" SetCookie ] NoContent)
418
436
437+data DummyRoutes mode = DummyRoutes
438+ { dummyInt :: mode :- "dummy" :> Get '[JSON] Int
439+ } deriving Generic
440+
419jwtOnlyApi :: Proxy (API '[Servant.Auth.Server.JWT])
441jwtOnlyApi :: Proxy (API '[Servant.Auth.Server.JWT])
420jwtOnlyApi = Proxy
442jwtOnlyApi = Proxy
421
443
@@ -476,6 +498,7 @@ server ccfg =
476 (\authResult -> case authResult of
498 (\authResult -> case authResult of
477 Authenticated usr -> getInt usr
499 Authenticated usr -> getInt usr
478 :<|> postInt usr
500 :<|> postInt usr
501+ :<|> DummyRoutes { dummyInt = getInt usr }
479 :<|> getHeaderInt
502 :<|> getHeaderInt
480#if MIN_VERSION_servant_server(0,15,0)
503#if MIN_VERSION_servant_server(0,15,0)
481 :<|> return (S.source ["bytestring"])
504 :<|> return (S.source ["bytestring"])
@@ -483,6 +506,11 @@ server ccfg =
483 :<|> raw
506 :<|> raw
484 Indefinite -> throwAll err401
507 Indefinite -> throwAll err401
485 _ -> throwAll err403
508 _ -> throwAll err403
509+ ) :<|>
510+ (\authResult -> case authResult of
511+ Authenticated usr -> respond (WithStatus @200 (42 :: Int))
512+ Indefinite -> respond (WithStatus @401 $ pack "Authentication required")
513+ _ -> respond (WithStatus @403 $ pack "Forbidden")
486 )
514 )
487 :<|> getLogin
515 :<|> getLogin
488 :<|> getLogout
516 :<|> getLogout
@@ -533,7 +561,7 @@ addJwtToHeader jwt = case jwt of
533 $ defaults & header "Authorization" .~ ["Bearer " <> BSL.toStrict v]
561 $ defaults & header "Authorization" .~ ["Bearer " <> BSL.toStrict v]
534
562
535createJWT :: JWK -> JWSHeader () -> ClaimsSet -> IO (Either Error Crypto.JWT.SignedJWT)
563createJWT :: JWK -> JWSHeader () -> ClaimsSet -> IO (Either Error Crypto.JWT.SignedJWT)
536−createJWT k a b = runExceptT $ signClaims k a b
564+createJWT k a b = runJOSE $ signClaims k a b
537
565
538addJwtToCookie :: ToCompact a => CookieSettings -> Either Error a -> IO Options
566addJwtToCookie :: ToCompact a => CookieSettings -> Either Error a -> IO Options
539addJwtToCookie ccfg jwt = case jwt >>= (return . encodeCompact) of
567addJwtToCookie ccfg jwt = case jwt >>= (return . encodeCompact) of
@@ -563,6 +591,12 @@ shouldMatchCookieNames cj patterns
563 = fmap cookie_name (destroyCookieJar cj)
591 = fmap cookie_name (destroyCookieJar cj)
564 `shouldMatchList` patterns
592 `shouldMatchList` patterns
565
593
594+shouldNotHaveCookies :: HCli.CookieJar -> [BS.ByteString] -> Expectation
595+shouldNotHaveCookies cj patterns =
596+ sequence_ $ (\cookieName -> cookieNames `shouldNotContain` [cookieName]) <$> patterns
597+ where cookieNames :: [BS.ByteString]
598+ cookieNames = cookie_name <$> destroyCookieJar cj
599+
566shouldMatchCookieNameValues :: HCli.CookieJar -> [(BS.ByteString, BS.ByteString)] -> Expectation
600shouldMatchCookieNameValues :: HCli.CookieJar -> [(BS.ByteString, BS.ByteString)] -> Expectation
567shouldMatchCookieNameValues cj patterns
601shouldMatchCookieNameValues cj patterns
568 = fmap ((,) <$> cookie_name <*> cookie_value) (destroyCookieJar cj)
602 = fmap ((,) <$> cookie_name <*> cookie_value) (destroyCookieJar cj)
servant-auth/servant-auth-swagger/servant-auth-swagger.cabal+10−14
@@ -1,3 +1,4 @@
1+cabal-version: 2.2
1name: servant-auth-swagger
2name: servant-auth-swagger
2version: 0.2.10.1
3version: 0.2.10.1
3synopsis: servant-swagger/servant-auth compatibility
4synopsis: servant-swagger/servant-auth compatibility
@@ -6,9 +7,9 @@ description: This package provides instances that allow generating swagger2 s
6 APIs that use
7 APIs that use
7 <https://hackage.haskell.org/package/servant-auth servant-auth's> @Auth@ combinator.
8 <https://hackage.haskell.org/package/servant-auth servant-auth's> @Auth@ combinator.
8 .
9 .
9− For a quick overview of the usage, see the <http://github.com/haskell-servant/servant/servant-auth#readme README>.
10+ For a quick overview of the usage, see the <https://github.com/haskell-servant/servant/tree/master/servant-auth#readme README>.
10category: Web, Servant, Authentication
11category: Web, Servant, Authentication
11−homepage: http://github.com/haskell-servant/servant/servant-auth#readme
12+homepage: https://github.com/haskell-servant/servant/tree/master/servant-auth#readme
12bug-reports: https://github.com/haskell-servant/servant/issues
13bug-reports: https://github.com/haskell-servant/servant/issues
13author: Julian K. Arni
14author: Julian K. Arni
14maintainer: jkarni@gmail.com
15maintainer: jkarni@gmail.com
@@ -17,7 +18,6 @@ license: BSD-3-Clause
17license-file: LICENSE
18license-file: LICENSE
18tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4
19tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4
19build-type: Simple
20build-type: Simple
20−cabal-version: >= 1.10
21extra-source-files:
21extra-source-files:
22 CHANGELOG.md
22 CHANGELOG.md
23
23
@@ -31,15 +31,13 @@ library
31 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
31 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
32 ghc-options: -Wall
32 ghc-options: -Wall
33 build-depends:
33 build-depends:
34− base >= 4.10 && < 4.16
34+ base >= 4.10 && < 4.18
35− , text >= 1.2.3.0 && < 1.3
35+ , text >= 1.2.3.0 && < 2.1
36 , servant-swagger >= 1.1.5 && < 2
36 , servant-swagger >= 1.1.5 && < 2
37 , swagger2 >= 2.2.2 && < 3
37 , swagger2 >= 2.2.2 && < 3
38− , servant >= 0.13 && < 0.19
38+ , servant >= 0.13 && < 0.20
39 , servant-auth == 0.4.*
39 , servant-auth == 0.4.*
40− , lens >= 4.16.1 && < 5.1
40+ , lens >= 4.16.1 && < 5.3
41− if impl(ghc >= 9)
42− buildable: False
43 exposed-modules:
41 exposed-modules:
44 Servant.Auth.Swagger
42 Servant.Auth.Swagger
45 default-language: Haskell2010
43 default-language: Haskell2010
@@ -51,7 +49,7 @@ test-suite spec
51 test
49 test
52 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
50 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
53 ghc-options: -Wall
51 ghc-options: -Wall
54− build-tool-depends: hspec-discover:hspec-discover >= 2.5.5 && <2.9
52+ build-tool-depends: hspec-discover:hspec-discover >= 2.5.5 && <2.10
55 -- dependencies with bounds inherited from the library stanza
53 -- dependencies with bounds inherited from the library stanza
56 build-depends:
54 build-depends:
57 base
55 base
@@ -61,13 +59,11 @@ test-suite spec
61 , servant
59 , servant
62 , servant-auth
60 , servant-auth
63 , lens
61 , lens
64− if impl(ghc >= 9)
62+
65− buildable: False
66−
67 -- test dependencies
63 -- test dependencies
68 build-depends:
64 build-depends:
69 servant-auth-swagger
65 servant-auth-swagger
70− , hspec >= 2.5.5 && < 2.9
66+ , hspec >= 2.5.5 && < 2.10
71 , QuickCheck >= 2.11.3 && < 2.15
67 , QuickCheck >= 2.11.3 && < 2.15
72 other-modules:
68 other-modules:
73 Servant.Auth.SwaggerSpec
69 Servant.Auth.SwaggerSpec
servant-auth/servant-auth/servant-auth.cabal+9−9
@@ -1,5 +1,6 @@
1+cabal-version: 2.2
1name: servant-auth
2name: servant-auth
2−version: 0.4.0.0
3+version: 0.4.1.0
3synopsis: Authentication combinators for servant
4synopsis: Authentication combinators for servant
4description: This package provides an @Auth@ combinator for 'servant'. This combinator
5description: This package provides an @Auth@ combinator for 'servant'. This combinator
5 allows using different authentication schemes in a straightforward way,
6 allows using different authentication schemes in a straightforward way,
@@ -8,9 +9,9 @@ description: This package provides an @Auth@ combinator for 'servant'. This c
8 'servant-auth' additionally provides concrete authentication schemes, such
9 'servant-auth' additionally provides concrete authentication schemes, such
9 as Basic Access Authentication, JSON Web Tokens, and Cookies.
10 as Basic Access Authentication, JSON Web Tokens, and Cookies.
10 .
11 .
11− For more details on how to use this, see the <http://github.com/haskell-servant/servant/servant-auth#readme README>.
12+ For more details on how to use this, see the <https://github.com/haskell-servant/servant/tree/master/servant-auth#readme README>.
12category: Web, Servant, Authentication
13category: Web, Servant, Authentication
13−homepage: http://github.com/haskell-servant/servant/servant-auth#readme
14+homepage: https://github.com/haskell-servant/servant/tree/master/servant-auth#readme
14bug-reports: https://github.com/haskell-servant/servant/issues
15bug-reports: https://github.com/haskell-servant/servant/issues
15author: Julian K. Arni
16author: Julian K. Arni
16maintainer: jkarni@gmail.com
17maintainer: jkarni@gmail.com
@@ -19,7 +20,6 @@ license: BSD-3-Clause
19license-file: LICENSE
20license-file: LICENSE
20tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
21tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
21build-type: Simple
22build-type: Simple
22−cabal-version: >= 1.10
23extra-source-files:
23extra-source-files:
24 CHANGELOG.md
24 CHANGELOG.md
25
25
@@ -33,13 +33,13 @@ library
33 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
33 default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
34 ghc-options: -Wall
34 ghc-options: -Wall
35 build-depends:
35 build-depends:
36− base >= 4.10 && < 4.16
36+ base >= 4.10 && < 4.18
37 , containers >= 0.6 && < 0.7
37 , containers >= 0.6 && < 0.7
38 , aeson >= 1.3.1.1 && < 3
38 , aeson >= 1.3.1.1 && < 3
39− , jose >= 0.7.0.0 && < 0.10
39+ , jose >= 0.10 && < 0.11
40− , lens >= 4.16.1 && < 5.1
40+ , lens >= 4.16.1 && < 5.3
41− , servant >= 0.15 && < 0.19
41+ , servant >= 0.15 && < 0.20
42− , text >= 1.2.3.0 && < 1.3
42+ , text >= 1.2.3.0 && < 2.1
43 , unordered-containers >= 0.2.9.0 && < 0.3
43 , unordered-containers >= 0.2.9.0 && < 0.3
44 exposed-modules:
44 exposed-modules:
45 Servant.Auth
45 Servant.Auth
servant-auth/servant-auth/src/Servant/Auth.hs+1−1
@@ -27,7 +27,7 @@ instance HasLink sub => HasLink (Auth (tag :: [*]) value :> sub) where
27
27
28-- ** Combinators
28-- ** Combinators
29
29
30−-- | A JSON Web Token (JWT) in the the Authorization header:
30+-- | A JSON Web Token (JWT) in the Authorization header:
31--
31--
32-- @Authorization: Bearer \<token\>@
32-- @Authorization: Bearer \<token\>@
33--
33--
servant-client-core/CHANGELOG.md+31
@@ -1,6 +1,37 @@
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-client-core/CHANGELOG.md)
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-client-core/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
3
3
4+Package versions follow the [Package Versioning Policy](https://pvp.haskell.org/): in A.B.C, bumps to either A or B represent major versions.
5+
6+0.19
7+----
8+
9+### Significant changes
10+
11+- Drop support for GHC < 8.6.
12+- Support GHC 9.0 (GHC 9.2 should work as well, but isn't fully tested yet).
13+- Support Aeson 2 ([#1475](https://github.com/haskell-servant/servant/pull/1475)),
14+ which fixes a [DOS vulnerability](https://github.com/haskell/aeson/issues/864)
15+ related to hash collisions.
16+- Add `NamedRoutes` combinator, making support for records first-class in Servant
17+ ([#1388](https://github.com/haskell-servant/servant/pull/1388)).
18+- Add custom type errors for partially applied combinators
19+ ([#1289](https://github.com/haskell-servant/servant/pull/1289),
20+ [#1486](https://github.com/haskell-servant/servant/pull/1486)).
21+- *servant-client* / *servant-client-core* / *servant-http-streams*: Fix
22+ erroneous behavior, where only 2XX status codes would be considered
23+ successful, irrelevant of the status parameter specified by the verb
24+ combinator. ([#1469](https://github.com/haskell-servant/servant/pull/1469))
25+- *servant-client* / *servant-client-core*: Fix `Show` instance for
26+ `Servant.Client.Core.Request`.
27+- *servant-client* / *servant-client-core*: Allow passing arbitrary binary data
28+ in Query parameters.
29+ ([#1432](https://github.com/haskell-servant/servant/pull/1432)).
30+
31+### Other changes
32+
33+- Various version bumps.
34+
40.18.3
350.18.3
5------
36------
6
37
servant-client-core/servant-client-core.cabal+10−10
@@ -1,6 +1,6 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-client-core
2name: servant-client-core
3−version: 0.18.3
3+version: 0.19
4
4
5synopsis: Core functionality and class for client function generation for servant APIs
5synopsis: Core functionality and class for client function generation for servant APIs
6category: Servant, Web
6category: Servant, Web
@@ -50,14 +50,14 @@ library
50 --
50 --
51 -- note: mtl lower bound is so low because of GHC-7.8
51 -- note: mtl lower bound is so low because of GHC-7.8
52 build-depends:
52 build-depends:
53− base >= 4.9 && < 4.16
53+ base >= 4.9 && < 4.18
54 , bytestring >= 0.10.8.1 && < 0.12
54 , bytestring >= 0.10.8.1 && < 0.12
55 , constraints >= 0.2 && < 0.14
55 , constraints >= 0.2 && < 0.14
56 , containers >= 0.5.7.1 && < 0.7
56 , containers >= 0.5.7.1 && < 0.7
57 , deepseq >= 1.4.2.0 && < 1.5
57 , deepseq >= 1.4.2.0 && < 1.5
58− , text >= 1.2.3.0 && < 1.3
58+ , text >= 1.2.3.0 && < 2.1
59− , transformers >= 0.5.2.0 && < 0.6
59+ , transformers >= 0.5.2.0 && < 0.7
60− , template-haskell >= 2.11.1.0 && < 2.18
60+ , template-haskell >= 2.11.1.0 && < 2.20
61
61
62 if !impl(ghc >= 8.2)
62 if !impl(ghc >= 8.2)
63 build-depends:
63 build-depends:
@@ -65,13 +65,13 @@ library
65
65
66 -- Servant dependencies
66 -- Servant dependencies
67 build-depends:
67 build-depends:
68− servant >= 0.18.3 && <0.19
68+ servant >= 0.19
69
69
70 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
70 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
71 -- Here can be exceptions if we really need features from the newer versions.
71 -- Here can be exceptions if we really need features from the newer versions.
72 build-depends:
72 build-depends:
73 aeson >= 1.4.1.0 && < 3
73 aeson >= 1.4.1.0 && < 3
74− , base-compat >= 0.10.5 && < 0.12
74+ , base-compat >= 0.10.5 && < 0.13
75 , base64-bytestring >= 1.0.0.1 && < 1.3
75 , base64-bytestring >= 1.0.0.1 && < 1.3
76 , exceptions >= 0.10.0 && < 0.11
76 , exceptions >= 0.10.0 && < 0.11
77 , free >= 5.1 && < 5.2
77 , free >= 5.1 && < 5.2
@@ -104,8 +104,8 @@ test-suite spec
104 -- Additional dependencies
104 -- Additional dependencies
105 build-depends:
105 build-depends:
106 deepseq >= 1.4.2.0 && < 1.5
106 deepseq >= 1.4.2.0 && < 1.5
107− , hspec >= 2.6.0 && < 2.9
107+ , hspec >= 2.6.0 && < 2.10
108 , QuickCheck >= 2.12.6.1 && < 2.15
108 , QuickCheck >= 2.12.6.1 && < 2.15
109
109
110 build-tool-depends:
110 build-tool-depends:
111− hspec-discover:hspec-discover >= 2.6.0 && <2.9
111+ hspec-discover:hspec-discover >= 2.6.0 && <2.10
servant-client-core/src/Servant/Client/Core.hs+1
@@ -59,6 +59,7 @@ module Servant.Client.Core
59 , appendToPath
59 , appendToPath
60 , setRequestBodyLBS
60 , setRequestBodyLBS
61 , setRequestBody
61 , setRequestBody
62+ , encodeQueryParamValue
62 ) where
63 ) where
63import Servant.Client.Core.Auth
64import Servant.Client.Core.Auth
64import Servant.Client.Core.BaseUrl
65import Servant.Client.Core.BaseUrl
servant-client-core/src/Servant/Client/Core/HasClient.hs+72−33
@@ -33,9 +33,7 @@ import Control.Arrow
33import Control.Monad
33import Control.Monad
34 (unless)
34 (unless)
35import qualified Data.ByteString as BS
35import qualified Data.ByteString as BS
36−import Data.ByteString.Builder
37− (toLazyByteString)
38−import qualified Data.ByteString.Lazy as BL
36+import qualified Data.ByteString.Lazy as BL
39import Data.Either
37import Data.Either
40 (partitionEithers)
38 (partitionEithers)
41import Data.Constraint (Dict(..))
39import Data.Constraint (Dict(..))
@@ -65,7 +63,7 @@ import Data.Text
65import Data.Proxy
63import Data.Proxy
66 (Proxy (Proxy))
64 (Proxy (Proxy))
67import GHC.TypeLits
65import GHC.TypeLits
68− (KnownSymbol, symbolVal)
66+ (KnownNat, KnownSymbol, TypeError, symbolVal)
69import Network.HTTP.Types
67import Network.HTTP.Types
70 (Status)
68 (Status)
71import qualified Network.HTTP.Types as H
69import qualified Network.HTTP.Types as H
@@ -76,19 +74,22 @@ import Servant.API
76 FromSourceIO (..), Header', Headers (..), HttpVersion,
74 FromSourceIO (..), Header', Headers (..), HttpVersion,
77 IsSecure, MimeRender (mimeRender),
75 IsSecure, MimeRender (mimeRender),
78 MimeUnrender (mimeUnrender), NoContent (NoContent),
76 MimeUnrender (mimeUnrender), NoContent (NoContent),
79− NoContentVerb, QueryFlag, QueryParam', QueryParams, Raw,
77+ NoContentVerb, QueryFlag, QueryParam', QueryParams, Raw, RawM,
80 ReflectMethod (..), RemoteHost, ReqBody', SBoolI, Stream,
78 ReflectMethod (..), RemoteHost, ReqBody', SBoolI, Stream,
81 StreamBody', Summary, ToHttpApiData, ToSourceIO (..), Vault,
79 StreamBody', Summary, ToHttpApiData, ToSourceIO (..), Vault,
82− Verb, WithNamedContext, WithStatus (..), contentType, getHeadersHList,
80+ Verb, WithNamedContext, WithResource, WithStatus (..), contentType, getHeadersHList,
83 getResponse, toEncodedUrlPiece, toUrlPiece, NamedRoutes)
81 getResponse, toEncodedUrlPiece, toUrlPiece, NamedRoutes)
84import Servant.API.Generic
82import Servant.API.Generic
85 (GenericMode(..), ToServant, ToServantApi
83 (GenericMode(..), ToServant, ToServantApi
86 , GenericServant, toServant, fromServant)
84 , GenericServant, toServant, fromServant)
87import Servant.API.ContentTypes
85import Servant.API.ContentTypes
88 (contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender))
86 (contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender))
87+import Servant.API.Status
88+ (statusFromNat)
89import Servant.API.TypeLevel (FragmentUnique, AtLeastOneFragment)
89import Servant.API.TypeLevel (FragmentUnique, AtLeastOneFragment)
90import Servant.API.Modifiers
90import Servant.API.Modifiers
91 (FoldRequired, RequiredArgument, foldRequiredArgument)
91 (FoldRequired, RequiredArgument, foldRequiredArgument)
92+import Servant.API.TypeErrors
92import Servant.API.UVerb
93import Servant.API.UVerb
93 (HasStatus, HasStatuses (Statuses, statuses), UVerb, Union, Unique, inject, statusOf, foldMapUnion, matchUnion)
94 (HasStatus, HasStatuses (Statuses, statuses), UVerb, Union, Unique, inject, statusOf, foldMapUnion, matchUnion)
94
95
@@ -207,7 +208,7 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m api)
207 clientWithRoute pm (Proxy :: Proxy api)
208 clientWithRoute pm (Proxy :: Proxy api)
208 (appendToPath p req)
209 (appendToPath p req)
209
210
210− where p = (toUrlPiece val)
211+ where p = toEncodedUrlPiece val
211
212
212 hoistClientMonad pm _ f cl = \a ->
213 hoistClientMonad pm _ f cl = \a ->
213 hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
214 hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
@@ -242,7 +243,7 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m sublayout)
242 clientWithRoute pm (Proxy :: Proxy sublayout)
243 clientWithRoute pm (Proxy :: Proxy sublayout)
243 (foldl' (flip appendToPath) req ps)
244 (foldl' (flip appendToPath) req ps)
244
245
245− where ps = map (toUrlPiece) vals
246+ where ps = map toEncodedUrlPiece vals
246
247
247 hoistClientMonad pm _ f cl = \as ->
248 hoistClientMonad pm _ f cl = \as ->
248 hoistClientMonad pm (Proxy :: Proxy sublayout) f (cl as)
249 hoistClientMonad pm (Proxy :: Proxy sublayout) f (cl as)
@@ -250,10 +251,11 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m sublayout)
250instance {-# OVERLAPPABLE #-}
251instance {-# OVERLAPPABLE #-}
251 -- Note [Non-Empty Content Types]
252 -- Note [Non-Empty Content Types]
252 ( RunClient m, MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
253 ( RunClient m, MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
254+ , KnownNat status
253 ) => HasClient m (Verb method status cts' a) where
255 ) => HasClient m (Verb method status cts' a) where
254 type Client m (Verb method status cts' a) = m a
256 type Client m (Verb method status cts' a) = m a
255 clientWithRoute _pm Proxy req = do
257 clientWithRoute _pm Proxy req = do
256− response <- runRequest req
258+ response <- runRequestAcceptStatus (Just [status]) req
257 { requestAccept = fromList $ toList accept
259 { requestAccept = fromList $ toList accept
258 , requestMethod = method
260 , requestMethod = method
259 }
261 }
@@ -261,18 +263,20 @@ instance {-# OVERLAPPABLE #-}
261 where
263 where
262 accept = contentTypes (Proxy :: Proxy ct)
264 accept = contentTypes (Proxy :: Proxy ct)
263 method = reflectMethod (Proxy :: Proxy method)
265 method = reflectMethod (Proxy :: Proxy method)
266+ status = statusFromNat (Proxy :: Proxy status)
264
267
265 hoistClientMonad _ _ f ma = f ma
268 hoistClientMonad _ _ f ma = f ma
266
269
267instance {-# OVERLAPPING #-}
270instance {-# OVERLAPPING #-}
268− ( RunClient m, ReflectMethod method
271+ ( RunClient m, ReflectMethod method, KnownNat status
269 ) => HasClient m (Verb method status cts NoContent) where
272 ) => HasClient m (Verb method status cts NoContent) where
270 type Client m (Verb method status cts NoContent)
273 type Client m (Verb method status cts NoContent)
271 = m NoContent
274 = m NoContent
272 clientWithRoute _pm Proxy req = do
275 clientWithRoute _pm Proxy req = do
273− _response <- runRequest req { requestMethod = method }
276+ _response <- runRequestAcceptStatus (Just [status]) req { requestMethod = method }
274 return NoContent
277 return NoContent
275 where method = reflectMethod (Proxy :: Proxy method)
278 where method = reflectMethod (Proxy :: Proxy method)
279+ status = statusFromNat (Proxy :: Proxy status)
276
280
277 hoistClientMonad _ _ f ma = f ma
281 hoistClientMonad _ _ f ma = f ma
278
282
@@ -289,13 +293,13 @@ instance (RunClient m, ReflectMethod method) =>
289
293
290instance {-# OVERLAPPING #-}
294instance {-# OVERLAPPING #-}
291 -- Note [Non-Empty Content Types]
295 -- Note [Non-Empty Content Types]
292− ( RunClient m, MimeUnrender ct a, BuildHeadersTo ls
296+ ( RunClient m, MimeUnrender ct a, BuildHeadersTo ls, KnownNat status
293 , ReflectMethod method, cts' ~ (ct ': cts)
297 , ReflectMethod method, cts' ~ (ct ': cts)
294 ) => HasClient m (Verb method status cts' (Headers ls a)) where
298 ) => HasClient m (Verb method status cts' (Headers ls a)) where
295 type Client m (Verb method status cts' (Headers ls a))
299 type Client m (Verb method status cts' (Headers ls a))
296 = m (Headers ls a)
300 = m (Headers ls a)
297 clientWithRoute _pm Proxy req = do
301 clientWithRoute _pm Proxy req = do
298− response <- runRequest req
302+ response <- runRequestAcceptStatus (Just [status]) req
299 { requestMethod = method
303 { requestMethod = method
300 , requestAccept = fromList $ toList accept
304 , requestAccept = fromList $ toList accept
301 }
305 }
@@ -303,22 +307,26 @@ instance {-# OVERLAPPING #-}
303 return $ Headers { getResponse = val
307 return $ Headers { getResponse = val
304 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
308 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
305 }
309 }
310+ where
306− where method = reflectMethod (Proxy :: Proxy method)
311+ method = reflectMethod (Proxy :: Proxy method)
307− accept = contentTypes (Proxy :: Proxy ct)
312+ accept = contentTypes (Proxy :: Proxy ct)
313+ status = statusFromNat (Proxy :: Proxy status)
308
314
309 hoistClientMonad _ _ f ma = f ma
315 hoistClientMonad _ _ f ma = f ma
310
316
311instance {-# OVERLAPPING #-}
317instance {-# OVERLAPPING #-}
312− ( RunClient m, BuildHeadersTo ls, ReflectMethod method
318+ ( RunClient m, BuildHeadersTo ls, ReflectMethod method, KnownNat status
313 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
319 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
314 type Client m (Verb method status cts (Headers ls NoContent))
320 type Client m (Verb method status cts (Headers ls NoContent))
315 = m (Headers ls NoContent)
321 = m (Headers ls NoContent)
316 clientWithRoute _pm Proxy req = do
322 clientWithRoute _pm Proxy req = do
317− let method = reflectMethod (Proxy :: Proxy method)
318− response <- runRequest req { requestMethod = method }
323+ response <- runRequestAcceptStatus (Just [status]) req { requestMethod = method }
319 return $ Headers { getResponse = NoContent
324 return $ Headers { getResponse = NoContent
320 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
325 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
321 }
326 }
327+ where
328+ method = reflectMethod (Proxy :: Proxy method)
329+ status = statusFromNat (Proxy :: Proxy status)
322
330
323 hoistClientMonad _ _ f ma = f ma
331 hoistClientMonad _ _ f ma = f ma
324
332
@@ -561,7 +569,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api, SBoolI (FoldRequire
561 (Proxy :: Proxy mods) add (maybe req add) mparam
569 (Proxy :: Proxy mods) add (maybe req add) mparam
562 where
570 where
563 add :: a -> Request
571 add :: a -> Request
564− add param = appendToQueryString pname (Just $ encodeQueryParam param) req
572+ add param = appendToQueryString pname (Just $ encodeQueryParamValue param) req
565
573
566 pname :: Text
574 pname :: Text
567 pname = pack $ symbolVal (Proxy :: Proxy sym)
575 pname = pack $ symbolVal (Proxy :: Proxy sym)
@@ -569,9 +577,6 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api, SBoolI (FoldRequire
569 hoistClientMonad pm _ f cl = \arg ->
577 hoistClientMonad pm _ f cl = \arg ->
570 hoistClientMonad pm (Proxy :: Proxy api) f (cl arg)
578 hoistClientMonad pm (Proxy :: Proxy api) f (cl arg)
571
579
572−encodeQueryParam :: ToHttpApiData a => a -> BS.ByteString
573−encodeQueryParam = BL.toStrict . toLazyByteString . toEncodedUrlPiece
574−
575-- | If you use a 'QueryParams' in one of your endpoints in your API,
580-- | If you use a 'QueryParams' in one of your endpoints in your API,
576-- the corresponding querying function will automatically take
581-- the corresponding querying function will automatically take
577-- an additional argument, a list of values of the type specified
582-- an additional argument, a list of values of the type specified
@@ -613,7 +618,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api)
613 )
618 )
614
619
615 where pname = pack $ symbolVal (Proxy :: Proxy sym)
620 where pname = pack $ symbolVal (Proxy :: Proxy sym)
616− paramlist' = map (Just . encodeQueryParam) paramlist
621+ paramlist' = map (Just . encodeQueryParamValue) paramlist
617
622
618 hoistClientMonad pm _ f cl = \as ->
623 hoistClientMonad pm _ f cl = \as ->
619 hoistClientMonad pm (Proxy :: Proxy api) f (cl as)
624 hoistClientMonad pm (Proxy :: Proxy api) f (cl as)
@@ -669,6 +674,16 @@ instance RunClient m => HasClient m Raw where
669
674
670 hoistClientMonad _ _ f cl = \meth -> f (cl meth)
675 hoistClientMonad _ _ f cl = \meth -> f (cl meth)
671
676
677+instance RunClient m => HasClient m RawM where
678+ type Client m RawM
679+ = H.Method -> m Response
680+
681+ clientWithRoute :: Proxy m -> Proxy RawM -> Request -> Client m RawM
682+ clientWithRoute _pm Proxy req httpMethod = do
683+ runRequest req { requestMethod = httpMethod }
684+
685+ hoistClientMonad _ _ f cl = \meth -> f (cl meth)
686+
672-- | If you use a 'ReqBody' in one of your endpoints in your API,
687-- | If you use a 'ReqBody' in one of your endpoints in your API,
673-- the corresponding querying function will automatically take
688-- the corresponding querying function will automatically take
674-- an additional argument of the type specified by your 'ReqBody'.
689-- an additional argument of the type specified by your 'ReqBody'.
@@ -735,7 +750,7 @@ instance (KnownSymbol path, HasClient m api) => HasClient m (path :> api) where
735 clientWithRoute pm (Proxy :: Proxy api)
750 clientWithRoute pm (Proxy :: Proxy api)
736 (appendToPath p req)
751 (appendToPath p req)
737
752
738− where p = pack $ symbolVal (Proxy :: Proxy path)
753+ where p = toEncodedUrlPiece $ pack $ symbolVal (Proxy :: Proxy path)
739
754
740 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
755 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
741
756
@@ -771,6 +786,14 @@ instance HasClient m subapi =>
771
786
772 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy subapi) f cl
787 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy subapi) f cl
773
788
789+instance HasClient m subapi =>
790+ HasClient m (WithResource res :> subapi) where
791+
792+ type Client m (WithResource res :> subapi) = Client m subapi
793+ clientWithRoute pm Proxy = clientWithRoute pm (Proxy :: Proxy subapi)
794+
795+ hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy subapi) f cl
796+
774instance ( HasClient m api
797instance ( HasClient m api
775 ) => HasClient m (AuthProtect tag :> api) where
798 ) => HasClient m (AuthProtect tag :> api) where
776 type Client m (AuthProtect tag :> api)
799 type Client m (AuthProtect tag :> api)
@@ -784,7 +807,7 @@ instance ( HasClient m api
784
807
785-- | Ignore @'Fragment'@ in client functions.
808-- | Ignore @'Fragment'@ in client functions.
786-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
809-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
787−--
810+--
788-- Example:
811-- Example:
789--
812--
790-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
813-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
@@ -801,7 +824,7 @@ instance (AtLeastOneFragment api, FragmentUnique (Fragment a :> api), HasClient
801
824
802 type Client m (Fragment a :> api) = Client m api
825 type Client m (Fragment a :> api) = Client m api
803
826
804− clientWithRoute pm _ = clientWithRoute pm (Proxy :: Proxy api)
827+ clientWithRoute pm _ = clientWithRoute pm (Proxy :: Proxy api)
805
828
806 hoistClientMonad pm _ = hoistClientMonad pm (Proxy :: Proxy api)
829 hoistClientMonad pm _ = hoistClientMonad pm (Proxy :: Proxy api)
807
830
@@ -869,7 +892,7 @@ infixl 2 /:
869--
892--
870-- Example:
893-- Example:
871--
894--
872−-- @@
895+-- @
873-- type Api = NamedRoutes RootApi
896-- type Api = NamedRoutes RootApi
874--
897--
875-- data RootApi mode = RootApi
898-- data RootApi mode = RootApi
@@ -889,8 +912,8 @@ infixl 2 /:
889-- rootClient = client api
912-- rootClient = client api
890--
913--
891-- endpointClient :: ClientM Person
914-- endpointClient :: ClientM Person
892−-- endpointClient = client // subApi // endpoint
915+-- endpointClient = client \/\/ subApi \/\/ endpoint
893−-- @@
916+-- @
894(//) :: a -> (a -> b) -> b
917(//) :: a -> (a -> b) -> b
895x // f = f x
918x // f = f x
896
919
@@ -901,7 +924,7 @@ x // f = f x
901--
924--
902-- Example:
925-- Example:
903--
926--
904−-- @@
927+-- @
905-- type Api = NamedRoutes RootApi
928-- type Api = NamedRoutes RootApi
906--
929--
907-- data RootApi mode = RootApi
930-- data RootApi mode = RootApi
@@ -922,11 +945,11 @@ x // f = f x
922-- rootClient = client api
945-- rootClient = client api
923--
946--
924-- hello :: String -> ClientM String
947-- hello :: String -> ClientM String
925−-- hello name = rootClient // hello /: name
948+-- hello name = rootClient \/\/ hello \/: name
926--
949--
927-- endpointClient :: ClientM Person
950-- endpointClient :: ClientM Person
928−-- endpointClient = client // subApi /: "foobar123" // endpoint
951+-- endpointClient = client \/\/ subApi \/: "foobar123" \/\/ endpoint
929−-- @@
952+-- @
930(/:) :: (a -> b -> c) -> b -> a -> c
953(/:) :: (a -> b -> c) -> b -> a -> c
931(/:) = flip
954(/:) = flip
932
955
@@ -970,3 +993,19 @@ decodedAs response ct = do
970 Right val -> return val
993 Right val -> return val
971 where
994 where
972 accept = toList $ contentTypes ct
995 accept = toList $ contentTypes ct
996+
997+-------------------------------------------------------------------------------
998+-- Custom type errors
999+-------------------------------------------------------------------------------
1000+
1001+-- Erroring instance for HasClient' when a combinator is not fully applied
1002+instance (RunClient m, TypeError (PartialApplication HasClient arr)) => HasClient m ((arr :: a -> b) :> sub)
1003+ where
1004+ type Client m (arr :> sub) = TypeError (PartialApplication HasClient arr)
1005+ clientWithRoute _ _ _ = error "unreachable"
1006+ hoistClientMonad _ _ _ _ = error "unreachable"
1007+
1008+-- Erroring instances for 'HasClient' for unknown API combinators
1009+instance {-# OVERLAPPABLE #-} (RunClient m, TypeError (NoInstanceForSub (HasClient m) ty)) => HasClient m (ty :> sub)
1010+
1011+instance {-# OVERLAPPABLE #-} (RunClient m, TypeError (NoInstanceFor (HasClient m api))) => HasClient m api
servant-client-core/src/Servant/Client/Core/Request.hs+22−6
@@ -17,6 +17,7 @@ module Servant.Client.Core.Request (
17 addHeader,
17 addHeader,
18 appendToPath,
18 appendToPath,
19 appendToQueryString,
19 appendToQueryString,
20+ encodeQueryParamValue,
20 setRequestBody,
21 setRequestBody,
21 setRequestBodyLBS,
22 setRequestBodyLBS,
22 ) where
23 ) where
@@ -33,6 +34,8 @@ import Data.Bifunctor
33import Data.Bitraversable
34import Data.Bitraversable
34 (Bitraversable (..), bifoldMapDefault, bimapDefault)
35 (Bitraversable (..), bifoldMapDefault, bimapDefault)
35import qualified Data.ByteString as BS
36import qualified Data.ByteString as BS
37+import Data.ByteString.Builder
38+ (Builder)
36import qualified Data.ByteString.Builder as Builder
39import qualified Data.ByteString.Builder as Builder
37import qualified Data.ByteString.Lazy as LBS
40import qualified Data.ByteString.Lazy as LBS
38import qualified Data.Sequence as Seq
41import qualified Data.Sequence as Seq
@@ -78,12 +81,13 @@ instance (Show a, Show b) =>
78 . showString ", requestAccept = "
81 . showString ", requestAccept = "
79 . showsPrec 0 (requestAccept req)
82 . showsPrec 0 (requestAccept req)
80 . showString ", requestHeaders = "
83 . showString ", requestHeaders = "
81− . showsPrec 0 (redactSensitiveHeader <$> requestHeaders req))
84+ . showsPrec 0 (redactSensitiveHeader <$> requestHeaders req)
82 . showString ", requestHttpVersion = "
85 . showString ", requestHttpVersion = "
83 . showsPrec 0 (requestHttpVersion req)
86 . showsPrec 0 (requestHttpVersion req)
84 . showString ", requestMethod = "
87 . showString ", requestMethod = "
85 . showsPrec 0 (requestMethod req)
88 . showsPrec 0 (requestMethod req)
86 . showString "}"
89 . showString "}"
90+ )
87 where
91 where
88 redactSensitiveHeader :: Header -> Header
92 redactSensitiveHeader :: Header -> Header
89 redactSensitiveHeader ("Authorization", _) = ("Authorization", "<REDACTED>")
93 redactSensitiveHeader ("Authorization", _) = ("Authorization", "<REDACTED>")
@@ -110,7 +114,7 @@ instance (NFData path, NFData body) => NFData (RequestF body path) where
110 rnfB Nothing = ()
114 rnfB Nothing = ()
111 rnfB (Just (b, mt)) = rnf b `seq` mediaTypeRnf mt
115 rnfB (Just (b, mt)) = rnf b `seq` mediaTypeRnf mt
112
116
113−type Request = RequestF RequestBody Builder.Builder
117+type Request = RequestF RequestBody Builder
114
118
115-- | The request body. R replica of the @http-client@ @RequestBody@.
119-- | The request body. R replica of the @http-client@ @RequestBody@.
116data RequestBody
120data RequestBody
@@ -141,18 +145,30 @@ defaultRequest = Request
141 , requestMethod = methodGet
145 , requestMethod = methodGet
142 }
146 }
143
147
148+-- | Append extra path to the request being constructed.
149+--
150+-- Warning: This function assumes that the path fragment is already URL-encoded.
144−appendToPath :: Text -> Request -> Request
151+appendToPath :: Builder -> Request -> Request
145appendToPath p req
152appendToPath p req
146− = req { requestPath = requestPath req <> "/" <> toEncodedUrlPiece p }
153+ = req { requestPath = requestPath req <> "/" <> p }
147
154
155+-- | Append a query parameter to the request being constructed.
156+--
148−appendToQueryString :: Text -- ^ param name
157+appendToQueryString :: Text -- ^ query param name
149− -> Maybe BS.ByteString -- ^ param value
158+ -> Maybe BS.ByteString -- ^ query param value
150 -> Request
159 -> Request
151 -> Request
160 -> Request
152appendToQueryString pname pvalue req
161appendToQueryString pname pvalue req
153 = req { requestQueryString = requestQueryString req
162 = req { requestQueryString = requestQueryString req
154 Seq.|> (encodeUtf8 pname, pvalue)}
163 Seq.|> (encodeUtf8 pname, pvalue)}
155
164
165+-- | Encode a query parameter value.
166+--
167+encodeQueryParamValue :: ToHttpApiData a => a -> BS.ByteString
168+encodeQueryParamValue = LBS.toStrict . Builder.toLazyByteString . toEncodedUrlPiece
169+
170+-- | Add header to the request being constructed.
171+--
156addHeader :: ToHttpApiData a => HeaderName -> a -> Request -> Request
172addHeader :: ToHttpApiData a => HeaderName -> a -> Request -> Request
157addHeader name val req
173addHeader name val req
158 = req { requestHeaders = requestHeaders req Seq.|> (name, toHeader val)}
174 = req { requestHeaders = requestHeaders req Seq.|> (name, toHeader val)}
servant-client-core/test/Servant/Client/Core/RequestSpec.hs+12
@@ -10,10 +10,22 @@ import Data.List (isInfixOf)
10import Servant.Client.Core.Request
10import Servant.Client.Core.Request
11import Test.Hspec
11import Test.Hspec
12
12
13+newtype DataWithRequest = DataWithRequest (RequestF RequestBody ())
14+ deriving Show
15+
13spec :: Spec
16spec :: Spec
14spec = do
17spec = do
15 describe "Request" $ do
18 describe "Request" $ do
16 describe "show" $ do
19 describe "show" $ do
20+ it "has parenthesis correctly positioned" $ do
21+ let d = DataWithRequest (void defaultRequest)
22+ show d `shouldBe` "DataWithRequest (Request {requestPath = ()\
23+ \, requestQueryString = fromList []\
24+ \, requestBody = Nothing\
25+ \, requestAccept = fromList []\
26+ \, requestHeaders = fromList []\
27+ \, requestHttpVersion = HTTP/1.1\
28+ \, requestMethod = \"GET\"})"
17 it "redacts the authorization header" $ do
29 it "redacts the authorization header" $ do
18 let request = void $ defaultRequest { requestHeaders = pure ("authorization", "secret") }
30 let request = void $ defaultRequest { requestHeaders = pure ("authorization", "secret") }
19 isInfixOf "secret" (show request) `shouldBe` False
31 isInfixOf "secret" (show request) `shouldBe` False
servant-client-ghcjs/servant-client-ghcjs.cabal+5−5
@@ -1,4 +1,4 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-client-ghcjs
2name: servant-client-ghcjs
3version: 0.16
3version: 0.16
4synopsis:
4synopsis:
@@ -38,7 +38,7 @@ library
38 Servant.Client.Internal.XhrClient
38 Servant.Client.Internal.XhrClient
39
39
40 build-depends:
40 build-depends:
41− base >=4.11 && <4.12
41+ base >=4.11 && <5
42 , bytestring >=0.10 && <0.12
42 , bytestring >=0.10 && <0.12
43 , case-insensitive >=1.2.0.0 && <1.3.0.0
43 , case-insensitive >=1.2.0.0 && <1.3.0.0
44 , containers >=0.5 && <0.7
44 , containers >=0.5 && <0.7
@@ -48,7 +48,7 @@ library
48 , http-media >=0.6.2 && <0.9
48 , http-media >=0.6.2 && <0.9
49 , http-types >=0.12 && <0.13
49 , http-types >=0.12 && <0.13
50 , monad-control >=1.0.0.4 && <1.1
50 , monad-control >=1.0.0.4 && <1.1
51− , mtl >=2.2.2 && <2.3
51+ , mtl ^>=2.2.2 || ^>=2.3.1
52 , semigroupoids >=5.3 && <5.4
52 , semigroupoids >=5.3 && <5.4
53 , string-conversions >=0.3 && <0.5
53 , string-conversions >=0.3 && <0.5
54 , transformers >=0.3 && <0.6
54 , transformers >=0.3 && <0.6
@@ -56,8 +56,8 @@ library
56
56
57 -- strict, as we re-export stuff
57 -- strict, as we re-export stuff
58 build-depends:
58 build-depends:
59− servant >=0.16 && <0.17
59+ servant >=0.16 && <0.20
60− , servant-client-core >=0.16 && <0.16.1
60+ , servant-client-core >=0.16 && <0.20
61
61
62 hs-source-dirs: src
62 hs-source-dirs: src
63 default-language: Haskell2010
63 default-language: Haskell2010
servant-client-ghcjs/src/Servant/Client/Internal/XhrClient.hs+8−5
@@ -120,7 +120,7 @@ instance Exception StreamingNotSupportedException where
120 displayException _ = "streamingRequest: streaming is not supported!"
120 displayException _ = "streamingRequest: streaming is not supported!"
121
121
122instance RunClient ClientM where
122instance RunClient ClientM where
123− runRequest = performRequest
123+ runRequestAcceptStatus = performRequest
124 throwClientError = throwError
124 throwClientError = throwError
125
125
126runClientMOrigin :: ClientM a -> ClientEnv -> IO (Either ClientError a)
126runClientMOrigin :: ClientM a -> ClientEnv -> IO (Either ClientError a)
@@ -152,15 +152,18 @@ runClientM m = do
152
152
153 runClientMOrigin m (ClientEnv (BaseUrl protocol hostname port ""))
153 runClientMOrigin m (ClientEnv (BaseUrl protocol hostname port ""))
154
154
155−performRequest :: Request -> ClientM Response
155+performRequest :: Maybe [Status] -> Request -> ClientM Response
156−performRequest req = do
156+performRequest acceptStatus req = do
157 xhr <- liftIO initXhr
157 xhr <- liftIO initXhr
158 burl <- asks baseUrl
158 burl <- asks baseUrl
159 liftIO $ performXhr xhr burl req
159 liftIO $ performXhr xhr burl req
160 resp <- toResponse xhr
160 resp <- toResponse xhr
161
161
162− let status = statusCode (responseStatusCode resp)
162+ let status = responseStatusCode resp
163− unless (status >= 200 && status < 300) $ do
163+ goodStatus = case acceptStatus of
164+ Nothing -> statusIsSuccessful status
165+ Just good -> status `elem` good
166+ unless goodStatus $ do
164 let f b = (burl, BL.toStrict $ toLazyByteString b)
167 let f b = (burl, BL.toStrict $ toLazyByteString b)
165 throwError $ FailureResponse (bimap (const ()) f req) resp
168 throwError $ FailureResponse (bimap (const ()) f req) resp
166
169
servant-client/CHANGELOG.md+31
@@ -1,6 +1,37 @@
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-client/CHANGELOG.md)
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-client/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
3
3
4+Package versions follow the [Package Versioning Policy](https://pvp.haskell.org/): in A.B.C, bumps to either A or B represent major versions.
5+
6+0.19
7+----
8+
9+### Significant changes
10+
11+- Drop support for GHC < 8.6.
12+- Support GHC 9.0 (GHC 9.2 should work as well, but isn't fully tested yet).
13+- Support Aeson 2 ([#1475](https://github.com/haskell-servant/servant/pull/1475)),
14+ which fixes a [DOS vulnerability](https://github.com/haskell/aeson/issues/864)
15+ related to hash collisions.
16+- Add `NamedRoutes` combinator, making support for records first-class in Servant
17+ ([#1388](https://github.com/haskell-servant/servant/pull/1388)).
18+- Add custom type errors for partially applied combinators
19+ ([#1289](https://github.com/haskell-servant/servant/pull/1289),
20+ [#1486](https://github.com/haskell-servant/servant/pull/1486)).
21+- *servant-client* / *servant-client-core* / *servant-http-streams*: Fix
22+ erroneous behavior, where only 2XX status codes would be considered
23+ successful, irrelevant of the status parameter specified by the verb
24+ combinator. ([#1469](https://github.com/haskell-servant/servant/pull/1469))
25+- *servant-client* / *servant-client-core*: Fix `Show` instance for
26+ `Servant.Client.Core.Request`.
27+- *servant-client* / *servant-client-core*: Allow passing arbitrary binary data
28+ in Query parameters.
29+ ([#1432](https://github.com/haskell-servant/servant/pull/1432)).
30+
31+### Other changes
32+
33+- Various version bumps.
34+
40.18.3
350.18.3
5------
36------
6
37
servant-client/servant-client.cabal+15−14
@@ -1,6 +1,6 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-client
2name: servant-client
3−version: 0.18.3
3+version: 0.19
4
4
5synopsis: Automatic derivation of querying functions for servant
5synopsis: Automatic derivation of querying functions for servant
6category: Servant, Web
6category: Servant, Web
@@ -41,15 +41,15 @@ library
41 -- Bundled with GHC: Lower bound to not force re-installs
41 -- Bundled with GHC: Lower bound to not force re-installs
42 -- text and mtl are bundled starting with GHC-8.4
42 -- text and mtl are bundled starting with GHC-8.4
43 build-depends:
43 build-depends:
44− base >= 4.9 && < 4.16
44+ base >= 4.9 && < 4.18
45 , bytestring >= 0.10.8.1 && < 0.12
45 , bytestring >= 0.10.8.1 && < 0.12
46 , containers >= 0.5.7.1 && < 0.7
46 , containers >= 0.5.7.1 && < 0.7
47 , deepseq >= 1.4.2.0 && < 1.5
47 , deepseq >= 1.4.2.0 && < 1.5
48− , mtl >= 2.2.2 && < 2.3
48+ , mtl ^>= 2.2.2 || ^>= 2.3.1
49 , stm >= 2.4.5.1 && < 2.6
49 , stm >= 2.4.5.1 && < 2.6
50− , text >= 1.2.3.0 && < 1.3
50+ , text >= 1.2.3.0 && < 2.1
51− , time >= 1.6.0.1 && < 1.10
51+ , time >= 1.6.0.1 && < 1.13
52− , transformers >= 0.5.2.0 && < 0.6
52+ , transformers >= 0.5.2.0 && < 0.7
53
53
54 if !impl(ghc >= 8.2)
54 if !impl(ghc >= 8.2)
55 build-depends:
55 build-depends:
@@ -58,13 +58,13 @@ library
58 -- Servant dependencies.
58 -- Servant dependencies.
59 -- Strict dependency on `servant-client-core` as we re-export things.
59 -- Strict dependency on `servant-client-core` as we re-export things.
60 build-depends:
60 build-depends:
61− servant == 0.18.*
61+ servant >= 0.18 && < 0.20
62− , servant-client-core >= 0.18.3 && <0.18.4
62+ , servant-client-core >= 0.19 && < 0.19.1
63
63
64 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
64 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
65 -- Here can be exceptions if we really need features from the newer versions.
65 -- Here can be exceptions if we really need features from the newer versions.
66 build-depends:
66 build-depends:
67− base-compat >= 0.10.5 && < 0.12
67+ base-compat >= 0.10.5 && < 0.13
68 , http-client >= 0.5.13.1 && < 0.8
68 , http-client >= 0.5.13.1 && < 0.8
69 , http-media >= 0.7.1.3 && < 0.9
69 , http-media >= 0.7.1.3 && < 0.9
70 , http-types >= 0.12.2 && < 0.13
70 , http-types >= 0.12.2 && < 0.13
@@ -89,6 +89,7 @@ test-suite spec
89 main-is: Spec.hs
89 main-is: Spec.hs
90 other-modules:
90 other-modules:
91 Servant.BasicAuthSpec
91 Servant.BasicAuthSpec
92+ Servant.BrokenSpec
92 Servant.ClientTestUtils
93 Servant.ClientTestUtils
93 Servant.ConnectionErrorSpec
94 Servant.ConnectionErrorSpec
94 Servant.FailSpec
95 Servant.FailSpec
@@ -123,16 +124,16 @@ test-suite spec
123 -- Additional dependencies
124 -- Additional dependencies
124 build-depends:
125 build-depends:
125 entropy >= 0.4.1.3 && < 0.5
126 entropy >= 0.4.1.3 && < 0.5
126− , hspec >= 2.6.0 && < 2.9
127+ , hspec >= 2.6.0 && < 2.10
127 , HUnit >= 1.6.0.0 && < 1.7
128 , HUnit >= 1.6.0.0 && < 1.7
128 , network >= 2.8.0.0 && < 3.2
129 , network >= 2.8.0.0 && < 3.2
129 , QuickCheck >= 2.12.6.1 && < 2.15
130 , QuickCheck >= 2.12.6.1 && < 2.15
130− , servant == 0.18.*
131+ , servant == 0.19.*
131− , servant-server == 0.18.*
132+ , servant-server == 0.19.*
132 , tdigest >= 0.2 && < 0.3
133 , tdigest >= 0.2 && < 0.3
133
134
134 build-tool-depends:
135 build-tool-depends:
135− hspec-discover:hspec-discover >= 2.6.0 && < 2.9
136+ hspec-discover:hspec-discover >= 2.6.0 && < 2.10
136
137
137test-suite readme
138test-suite readme
138 type: exitcode-stdio-1.0
139 type: exitcode-stdio-1.0
servant-client/src/Servant/Client/Internal/HttpClient.hs+11−11
@@ -24,7 +24,7 @@ import Control.Monad
24import Control.Monad.Base
24import Control.Monad.Base
25 (MonadBase (..))
25 (MonadBase (..))
26import Control.Monad.Catch
26import Control.Monad.Catch
27− (MonadCatch, MonadThrow)
27+ (MonadCatch, MonadThrow, MonadMask)
28import Control.Monad.Error.Class
28import Control.Monad.Error.Class
29 (MonadError (..))
29 (MonadError (..))
30import Control.Monad.IO.Class
30import Control.Monad.IO.Class
@@ -63,7 +63,7 @@ import GHC.Generics
63import Network.HTTP.Media
63import Network.HTTP.Media
64 (renderHeader)
64 (renderHeader)
65import Network.HTTP.Types
65import Network.HTTP.Types
66− (hContentType, renderQuery, statusCode, urlEncode, Status)
66+ (hContentType, renderQuery, statusIsSuccessful, urlEncode, Status)
67import Servant.Client.Core
67import Servant.Client.Core
68
68
69import qualified Network.HTTP.Client as Client
69import qualified Network.HTTP.Client as Client
@@ -80,7 +80,7 @@ data ClientEnv
80 { manager :: Client.Manager
80 { manager :: Client.Manager
81 , baseUrl :: BaseUrl
81 , baseUrl :: BaseUrl
82 , cookieJar :: Maybe (TVar Client.CookieJar)
82 , cookieJar :: Maybe (TVar Client.CookieJar)
83− , makeClientRequest :: BaseUrl -> Request -> Client.Request
83+ , makeClientRequest :: BaseUrl -> Request -> IO Client.Request
84 -- ^ this function can be used to customize the creation of @http-client@ requests from @servant@ requests. Default value: 'defaultMakeClientRequest'
84 -- ^ this function can be used to customize the creation of @http-client@ requests from @servant@ requests. Default value: 'defaultMakeClientRequest'
85 -- Note that:
85 -- Note that:
86 -- 1. 'makeClientRequest' exists to allow overriding operational semantics e.g. 'responseTimeout' per request,
86 -- 1. 'makeClientRequest' exists to allow overriding operational semantics e.g. 'responseTimeout' per request,
@@ -136,7 +136,7 @@ newtype ClientM a = ClientM
136 { unClientM :: ReaderT ClientEnv (ExceptT ClientError IO) a }
136 { unClientM :: ReaderT ClientEnv (ExceptT ClientError IO) a }
137 deriving ( Functor, Applicative, Monad, MonadIO, Generic
137 deriving ( Functor, Applicative, Monad, MonadIO, Generic
138 , MonadReader ClientEnv, MonadError ClientError, MonadThrow
138 , MonadReader ClientEnv, MonadError ClientError, MonadThrow
139− , MonadCatch)
139+ , MonadCatch, MonadMask)
140
140
141instance MonadBase IO ClientM where
141instance MonadBase IO ClientM where
142 liftBase = ClientM . liftBase
142 liftBase = ClientM . liftBase
@@ -162,7 +162,7 @@ runClientM cm env = runExceptT $ flip runReaderT env $ unClientM cm
162performRequest :: Maybe [Status] -> Request -> ClientM Response
162performRequest :: Maybe [Status] -> Request -> ClientM Response
163performRequest acceptStatus req = do
163performRequest acceptStatus req = do
164 ClientEnv m burl cookieJar' createClientRequest <- ask
164 ClientEnv m burl cookieJar' createClientRequest <- ask
165− let clientRequest = createClientRequest burl req
165+ clientRequest <- liftIO $ createClientRequest burl req
166 request <- case cookieJar' of
166 request <- case cookieJar' of
167 Nothing -> pure clientRequest
167 Nothing -> pure clientRequest
168 Just cj -> liftIO $ do
168 Just cj -> liftIO $ do
@@ -179,10 +179,9 @@ performRequest acceptStatus req = do
179
179
180 response <- maybe (requestWithoutCookieJar m request) (requestWithCookieJar m request) cookieJar'
180 response <- maybe (requestWithoutCookieJar m request) (requestWithCookieJar m request) cookieJar'
181 let status = Client.responseStatus response
181 let status = Client.responseStatus response
182− status_code = statusCode status
183 ourResponse = clientResponseToResponse id response
182 ourResponse = clientResponseToResponse id response
184 goodStatus = case acceptStatus of
183 goodStatus = case acceptStatus of
185− Nothing -> status_code >= 200 && status_code < 300
184+ Nothing -> statusIsSuccessful status
186 Just good -> status `elem` good
185 Just good -> status `elem` good
187 unless goodStatus $ do
186 unless goodStatus $ do
188 throwError $ mkFailureResponse burl req ourResponse
187 throwError $ mkFailureResponse burl req ourResponse
@@ -230,8 +229,8 @@ clientResponseToResponse f r = Response
230-- | Create a @http-client@ 'Client.Request' from a @servant@ 'Request'
229-- | Create a @http-client@ 'Client.Request' from a @servant@ 'Request'
231-- The 'Client.host', 'Client.path' and 'Client.port' fields are extracted from the 'BaseUrl'
230-- The 'Client.host', 'Client.path' and 'Client.port' fields are extracted from the 'BaseUrl'
232-- otherwise the body, headers and query string are derived from the @servant@ 'Request'
231-- otherwise the body, headers and query string are derived from the @servant@ 'Request'
233−defaultMakeClientRequest :: BaseUrl -> Request -> Client.Request
232+defaultMakeClientRequest :: BaseUrl -> Request -> IO Client.Request
234−defaultMakeClientRequest burl r = Client.defaultRequest
233+defaultMakeClientRequest burl r = return Client.defaultRequest
235 { Client.method = requestMethod r
234 { Client.method = requestMethod r
236 , Client.host = fromString $ baseUrlHost burl
235 , Client.host = fromString $ baseUrlHost burl
237 , Client.port = baseUrlPort burl
236 , Client.port = baseUrlPort burl
@@ -247,7 +246,7 @@ defaultMakeClientRequest burl r = Client.defaultRequest
247 where
246 where
248 -- Content-Type and Accept are specified by requestBody and requestAccept
247 -- Content-Type and Accept are specified by requestBody and requestAccept
249 headers = filter (\(h, _) -> h /= "Accept" && h /= "Content-Type") $
248 headers = filter (\(h, _) -> h /= "Accept" && h /= "Content-Type") $
250− toList $requestHeaders r
249+ toList $ requestHeaders r
251
250
252 acceptHdr
251 acceptHdr
253 | null hs = Nothing
252 | null hs = Nothing
@@ -290,7 +289,8 @@ defaultMakeClientRequest burl r = Client.defaultRequest
290 Https -> True
289 Https -> True
291
290
292 -- Query string builder which does not do any encoding
291 -- Query string builder which does not do any encoding
292+ buildQueryString [] = mempty
293− buildQueryString = ("?" <>) . foldl' addQueryParam mempty
293+ buildQueryString qps = "?" <> foldl' addQueryParam mempty qps
294
294
295 addQueryParam qs (k, v) =
295 addQueryParam qs (k, v) =
296 qs <> (if BS.null qs then mempty else "&") <> urlEncode True k <> foldMap ("=" <>) v
296 qs <> (if BS.null qs then mempty else "&") <> urlEncode True k <> foldMap ("=" <>) v
servant-client/src/Servant/Client/Internal/HttpClient/Streaming.hs+21−11
@@ -24,7 +24,8 @@ import Control.DeepSeq
24 (NFData, force)
24 (NFData, force)
25import Control.Exception
25import Control.Exception
26 (evaluate, throwIO)
26 (evaluate, throwIO)
27−import Control.Monad ()
27+import Control.Monad
28+ (unless)
28import Control.Monad.Base
29import Control.Monad.Base
29 (MonadBase (..))
30 (MonadBase (..))
30import Control.Monad.Codensity
31import Control.Monad.Codensity
@@ -47,7 +48,7 @@ import Data.Time.Clock
47 (getCurrentTime)
48 (getCurrentTime)
48import GHC.Generics
49import GHC.Generics
49import Network.HTTP.Types
50import Network.HTTP.Types
50− (Status, statusCode)
51+ (Status, statusIsSuccessful)
51
52
52import qualified Network.HTTP.Client as Client
53import qualified Network.HTTP.Client as Client
53
54
@@ -140,7 +141,7 @@ performRequest :: Maybe [Status] -> Request -> ClientM Response
140performRequest acceptStatus req = do
141performRequest acceptStatus req = do
141 -- TODO: should use Client.withResponse here too
142 -- TODO: should use Client.withResponse here too
142 ClientEnv m burl cookieJar' createClientRequest <- ask
143 ClientEnv m burl cookieJar' createClientRequest <- ask
143− let clientRequest = createClientRequest burl req
144+ clientRequest <- liftIO $ createClientRequest burl req
144 request <- case cookieJar' of
145 request <- case cookieJar' of
145 Nothing -> pure clientRequest
146 Nothing -> pure clientRequest
146 Just cj -> liftIO $ do
147 Just cj -> liftIO $ do
@@ -163,10 +164,9 @@ performRequest acceptStatus req = do
163 now' <- getCurrentTime
164 now' <- getCurrentTime
164 atomically $ modifyTVar' cj (fst . Client.updateCookieJar response request now')
165 atomically $ modifyTVar' cj (fst . Client.updateCookieJar response request now')
165 let status = Client.responseStatus response
166 let status = Client.responseStatus response
166− status_code = statusCode status
167 ourResponse = clientResponseToResponse id response
167 ourResponse = clientResponseToResponse id response
168 goodStatus = case acceptStatus of
168 goodStatus = case acceptStatus of
169− Nothing -> status_code >= 200 && status_code < 300
169+ Nothing -> statusIsSuccessful status
170 Just good -> status `elem` good
170 Just good -> status `elem` good
171 unless goodStatus $ do
171 unless goodStatus $ do
172 throwError $ mkFailureResponse burl req ourResponse
172 throwError $ mkFailureResponse burl req ourResponse
@@ -175,17 +175,27 @@ performRequest acceptStatus req = do
175-- | TODO: support UVerb ('acceptStatus' argument, like in 'performRequest' above).
175-- | TODO: support UVerb ('acceptStatus' argument, like in 'performRequest' above).
176performWithStreamingRequest :: Request -> (StreamingResponse -> IO a) -> ClientM a
176performWithStreamingRequest :: Request -> (StreamingResponse -> IO a) -> ClientM a
177performWithStreamingRequest req k = do
177performWithStreamingRequest req k = do
178− m <- asks manager
178+ ClientEnv m burl cookieJar' createClientRequest <- ask
179− burl <- asks baseUrl
179+ clientRequest <- liftIO $ createClientRequest burl req
180− createClientRequest <- asks makeClientRequest
180+ request <- case cookieJar' of
181− let request = createClientRequest burl req
181+ Nothing -> pure clientRequest
182+ Just cj -> liftIO $ do
183+ now <- getCurrentTime
184+ atomically $ do
185+ oldCookieJar <- readTVar cj
186+ let (newRequest, newCookieJar) =
187+ Client.insertCookiesIntoRequest
188+ clientRequest
189+ oldCookieJar
190+ now
191+ writeTVar cj newCookieJar
192+ pure newRequest
182 ClientM $ lift $ lift $ Codensity $ \k1 ->
193 ClientM $ lift $ lift $ Codensity $ \k1 ->
183 Client.withResponse request m $ \res -> do
194 Client.withResponse request m $ \res -> do
184 let status = Client.responseStatus res
195 let status = Client.responseStatus res
185− status_code = statusCode status
186
196
187 -- we throw FailureResponse in IO :(
197 -- we throw FailureResponse in IO :(
188− unless (status_code >= 200 && status_code < 300) $ do
198+ unless (statusIsSuccessful status) $ do
189 b <- BSL.fromChunks <$> Client.brConsume (Client.responseBody res)
199 b <- BSL.fromChunks <$> Client.brConsume (Client.responseBody res)
190 throwIO $ mkFailureResponse burl req (clientResponseToResponse (const b) res)
200 throwIO $ mkFailureResponse burl req (clientResponseToResponse (const b) res)
191
201
servant-client/test/Servant/BrokenSpec.hsnew file+71
@@ -0,0 +1,71 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE TypeOperators #-}
3+{-# OPTIONS_GHC -freduction-depth=100 #-}
4+{-# OPTIONS_GHC -fno-warn-orphans #-}
5+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
6+
7+module Servant.BrokenSpec (spec) where
8+
9+import Prelude ()
10+import Prelude.Compat
11+
12+import Data.Monoid ()
13+import Data.Proxy
14+import qualified Network.HTTP.Types as HTTP
15+import Test.Hspec
16+
17+import Servant.API
18+ ((:<|>) ((:<|>)), (:>), JSON, Verb, Get, StdMethod (GET))
19+import Servant.Client
20+import Servant.ClientTestUtils
21+import Servant.Server
22+
23+-- * api for testing inconsistencies between client and server
24+
25+type Get201 = Verb 'GET 201
26+type Get301 = Verb 'GET 301
27+
28+type BrokenAPI =
29+ -- the server should respond with 200, but returns 201
30+ "get200" :> Get201 '[JSON] ()
31+ -- the server should respond with 307, but returns 301
32+ :<|> "get307" :> Get301 '[JSON] ()
33+
34+brokenApi :: Proxy BrokenAPI
35+brokenApi = Proxy
36+
37+brokenServer :: Application
38+brokenServer = serve brokenApi (pure () :<|> pure ())
39+
40+type PublicAPI =
41+ -- the client expects 200
42+ "get200" :> Get '[JSON] ()
43+ -- the client expects 307
44+ :<|> "get307" :> Get307 '[JSON] ()
45+
46+publicApi :: Proxy PublicAPI
47+publicApi = Proxy
48+
49+get200Client :: ClientM ()
50+get307Client :: ClientM ()
51+get200Client :<|> get307Client = client publicApi
52+
53+
54+spec :: Spec
55+spec = describe "Servant.BrokenSpec" $ do
56+ brokenSpec
57+
58+brokenSpec :: Spec
59+brokenSpec = beforeAll (startWaiApp brokenServer) $ afterAll endWaiApp $ do
60+ context "client returns errors for inconsistencies between client and server api" $ do
61+ it "reports FailureResponse with wrong 2xx status code" $ \(_, baseUrl) -> do
62+ res <- runClient get200Client baseUrl
63+ case res of
64+ Left (FailureResponse _ r) | responseStatusCode r == HTTP.status201 -> return ()
65+ _ -> fail $ "expected 201 broken response, but got " <> show res
66+
67+ it "reports FailureResponse with wrong 3xx status code" $ \(_, baseUrl) -> do
68+ res <- runClient get307Client baseUrl
69+ case res of
70+ Left (FailureResponse _ r) | responseStatusCode r == HTTP.status301 -> return ()
71+ _ -> fail $ "expected 301 broken response, but got " <> show res
servant-client/test/Servant/ClientTestUtils.hs+27−4
@@ -64,7 +64,7 @@ import Servant.API
64 JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender),
64 JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender),
65 NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam,
65 NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam,
66 QueryParams, Raw, ReqBody, StdMethod (GET), ToHttpApiData (..), UVerb, Union,
66 QueryParams, Raw, ReqBody, StdMethod (GET), ToHttpApiData (..), UVerb, Union,
67− WithStatus (WithStatus), NamedRoutes, addHeader)
67+ Verb, WithStatus (WithStatus), NamedRoutes, addHeader)
68import Servant.API.Generic ((:-))
68import Servant.API.Generic ((:-))
69import Servant.Client
69import Servant.Client
70import qualified Servant.Client.Core.Auth as Auth
70import qualified Servant.Client.Core.Auth as Auth
@@ -118,9 +118,16 @@ data OtherRoutes mode = OtherRoutes
118 { something :: mode :- "something" :> Get '[JSON] [String]
118 { something :: mode :- "something" :> Get '[JSON] [String]
119 } deriving Generic
119 } deriving Generic
120
120
121+-- Get for HTTP 307 Temporary Redirect
122+type Get307 = Verb 'GET 307
123+
121type Api =
124type Api =
122 Get '[JSON] Person
125 Get '[JSON] Person
123 :<|> "get" :> Get '[JSON] Person
126 :<|> "get" :> Get '[JSON] Person
127+ -- This endpoint returns a response with status code 307 Temporary Redirect,
128+ -- different from the ones in the 2xx successful class, to test derivation
129+ -- of clients' api.
130+ :<|> "get307" :> Get307 '[PlainText] Text
124 :<|> "deleteEmpty" :> DeleteNoContent
131 :<|> "deleteEmpty" :> DeleteNoContent
125 :<|> "capture" :> Capture "name" String :> Get '[JSON,FormUrlEncoded] Person
132 :<|> "capture" :> Capture "name" String :> Get '[JSON,FormUrlEncoded] Person
126 :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] [Person]
133 :<|> "captureAll" :> CaptureAll "names" String :> Get '[JSON] [Person]
@@ -153,13 +160,14 @@ type Api =
153 WithStatus 301 Text]
160 WithStatus 301 Text]
154 :<|> "uverb-get-created" :> UVerb 'GET '[PlainText] '[WithStatus 201 Person]
161 :<|> "uverb-get-created" :> UVerb 'GET '[PlainText] '[WithStatus 201 Person]
155 :<|> NamedRoutes RecordRoutes
162 :<|> NamedRoutes RecordRoutes
156−
163+ :<|> "captureVerbatim" :> Capture "someString" Verbatim :> Get '[PlainText] Text
157
164
158api :: Proxy Api
165api :: Proxy Api
159api = Proxy
166api = Proxy
160
167
161getRoot :: ClientM Person
168getRoot :: ClientM Person
162getGet :: ClientM Person
169getGet :: ClientM Person
170+getGet307 :: ClientM Text
163getDeleteEmpty :: ClientM NoContent
171getDeleteEmpty :: ClientM NoContent
164getCapture :: String -> ClientM Person
172getCapture :: String -> ClientM Person
165getCaptureAll :: [String] -> ClientM [Person]
173getCaptureAll :: [String] -> ClientM [Person]
@@ -186,6 +194,7 @@ recordRoutes :: RecordRoutes (AsClientT ClientM)
186
194
187getRoot
195getRoot
188 :<|> getGet
196 :<|> getGet
197+ :<|> getGet307
189 :<|> getDeleteEmpty
198 :<|> getDeleteEmpty
190 :<|> getCapture
199 :<|> getCapture
191 :<|> getCaptureAll
200 :<|> getCaptureAll
@@ -206,12 +215,14 @@ getRoot
206 :<|> EmptyClient
215 :<|> EmptyClient
207 :<|> uverbGetSuccessOrRedirect
216 :<|> uverbGetSuccessOrRedirect
208 :<|> uverbGetCreated
217 :<|> uverbGetCreated
209− :<|> recordRoutes = client api
218+ :<|> recordRoutes
219+ :<|> captureVerbatim = client api
210
220
211server :: Application
221server :: Application
212server = serve api (
222server = serve api (
213 return carol
223 return carol
214 :<|> return alice
224 :<|> return alice
225+ :<|> return "redirecting"
215 :<|> return NoContent
226 :<|> return NoContent
216 :<|> (\ name -> return $ Person name 0)
227 :<|> (\ name -> return $ Person name 0)
217 :<|> (\ names -> return (zipWith Person names [0..]))
228 :<|> (\ names -> return (zipWith Person names [0..]))
@@ -250,8 +261,11 @@ server = serve api (
250 { something = pure ["foo", "bar", "pweet"]
261 { something = pure ["foo", "bar", "pweet"]
251 }
262 }
252 }
263 }
264+ :<|> pure . decodeUtf8 . unVerbatim
253 )
265 )
254
266
267+-- * api for testing failures
268+
255type FailApi =
269type FailApi =
256 "get" :> Raw
270 "get" :> Raw
257 :<|> "capture" :> Capture "name" String :> Raw
271 :<|> "capture" :> Capture "name" String :> Raw
@@ -266,7 +280,7 @@ failServer = serve failApi (
266 :<|> (\ _capture -> Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "application/json")] "")
280 :<|> (\ _capture -> Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "application/json")] "")
267 :<|> (Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "fooooo")] "")
281 :<|> (Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "fooooo")] "")
268 :<|> (Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "application/x-www-form-urlencoded"), ("X-Example1", "1"), ("X-Example2", "foo")] "")
282 :<|> (Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "application/x-www-form-urlencoded"), ("X-Example1", "1"), ("X-Example2", "foo")] "")
269− )
283+ )
270
284
271-- * basic auth stuff
285-- * basic auth stuff
272
286
@@ -359,3 +373,12 @@ instance ToHttpApiData UrlEncodedByteString where
359
373
360instance FromHttpApiData UrlEncodedByteString where
374instance FromHttpApiData UrlEncodedByteString where
361 parseUrlPiece = pure . UrlEncodedByteString . HTTP.urlDecode True . encodeUtf8
375 parseUrlPiece = pure . UrlEncodedByteString . HTTP.urlDecode True . encodeUtf8
376+
377+newtype Verbatim = Verbatim { unVerbatim :: ByteString }
378+
379+instance ToHttpApiData Verbatim where
380+ toEncodedUrlPiece = byteString . unVerbatim
381+ toUrlPiece = decodeUtf8 . unVerbatim
382+
383+instance FromHttpApiData Verbatim where
384+ parseUrlPiece = pure . Verbatim . encodeUtf8
servant-client/test/Servant/FailSpec.hs+3−3
@@ -38,14 +38,14 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
38
38
39 context "client returns errors appropriately" $ do
39 context "client returns errors appropriately" $ do
40 it "reports FailureResponse" $ \(_, baseUrl) -> do
40 it "reports FailureResponse" $ \(_, baseUrl) -> do
41− let (_ :<|> _ :<|> getDeleteEmpty :<|> _) = client api
41+ let (_ :<|> _ :<|> _ :<|> getDeleteEmpty :<|> _) = client api
42 Left res <- runClient getDeleteEmpty baseUrl
42 Left res <- runClient getDeleteEmpty baseUrl
43 case res of
43 case res of
44 FailureResponse _ r | responseStatusCode r == HTTP.status404 -> return ()
44 FailureResponse _ r | responseStatusCode r == HTTP.status404 -> return ()
45 _ -> fail $ "expected 404 response, but got " <> show res
45 _ -> fail $ "expected 404 response, but got " <> show res
46
46
47 it "reports DecodeFailure" $ \(_, baseUrl) -> do
47 it "reports DecodeFailure" $ \(_, baseUrl) -> do
48− let (_ :<|> _ :<|> _ :<|> getCapture :<|> _) = client api
48+ let (_ :<|> _ :<|> _ :<|> _ :<|> getCapture :<|> _) = client api
49 Left res <- runClient (getCapture "foo") baseUrl
49 Left res <- runClient (getCapture "foo") baseUrl
50 case res of
50 case res of
51 DecodeFailure _ _ -> return ()
51 DecodeFailure _ _ -> return ()
@@ -72,7 +72,7 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
72 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
72 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
73
73
74 it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do
74 it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do
75− let (_ :<|> _ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
75+ let (_ :<|> _ :<|> _ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
76 Left res <- runClient (getBody alice) baseUrl
76 Left res <- runClient (getBody alice) baseUrl
77 case res of
77 case res of
78 InvalidContentTypeHeader _ -> return ()
78 InvalidContentTypeHeader _ -> return ()
servant-client/test/Servant/SuccessSpec.hs+21−6
@@ -36,6 +36,8 @@ import Data.Maybe
36import Data.Monoid ()
36import Data.Monoid ()
37import Data.Text
37import Data.Text
38 (Text)
38 (Text)
39+import Data.Text.Encoding
40+ (encodeUtf8)
39import qualified Network.HTTP.Client as C
41import qualified Network.HTTP.Client as C
40import qualified Network.HTTP.Types as HTTP
42import qualified Network.HTTP.Types as HTTP
41import Test.Hspec
43import Test.Hspec
@@ -59,11 +61,15 @@ spec = describe "Servant.SuccessSpec" $ do
59
61
60successSpec :: Spec
62successSpec :: Spec
61successSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
63successSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
62− it "Servant.API.Get root" $ \(_, baseUrl) -> do
64+ describe "Servant.API.Get" $ do
65+ it "get root endpoint" $ \(_, baseUrl) -> do
63− left show <$> runClient getRoot baseUrl `shouldReturn` Right carol
66+ left show <$> runClient getRoot baseUrl `shouldReturn` Right carol
64
67
65− it "Servant.API.Get" $ \(_, baseUrl) -> do
68+ it "get simple endpoint" $ \(_, baseUrl) -> do
66− left show <$> runClient getGet baseUrl `shouldReturn` Right alice
69+ left show <$> runClient getGet baseUrl `shouldReturn` Right alice
70+
71+ it "get redirection endpoint" $ \(_, baseUrl) -> do
72+ left show <$> runClient getGet307 baseUrl `shouldReturn` Right "redirecting"
67
73
68 describe "Servant.API.Delete" $ do
74 describe "Servant.API.Delete" $ do
69 it "allows empty content type" $ \(_, baseUrl) -> do
75 it "allows empty content type" $ \(_, baseUrl) -> do
@@ -111,6 +117,7 @@ successSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
111
117
112 it "Servant.API.Fragment" $ \(_, baseUrl) -> do
118 it "Servant.API.Fragment" $ \(_, baseUrl) -> do
113 left id <$> runClient getFragment baseUrl `shouldReturn` Right alice
119 left id <$> runClient getFragment baseUrl `shouldReturn` Right alice
120+
114 it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
121 it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
115 res <- runClient (getRawSuccess HTTP.methodGet) baseUrl
122 res <- runClient (getRawSuccess HTTP.methodGet) baseUrl
116 case res of
123 case res of
@@ -155,8 +162,9 @@ successSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
155 mgr <- C.newManager C.defaultManagerSettings
162 mgr <- C.newManager C.defaultManagerSettings
156 -- In proper situation, extra headers should probably be visible in API type.
163 -- In proper situation, extra headers should probably be visible in API type.
157 -- However, testing for response timeout is difficult, so we test with something which is easy to observe
164 -- However, testing for response timeout is difficult, so we test with something which is easy to observe
158− let createClientRequest url r = (defaultMakeClientRequest url r) { C.requestHeaders = [("X-Added-Header", "XXX")] }
165+ let createClientRequest url r = fmap (\req -> req { C.requestHeaders = [("X-Added-Header", "XXX")] })
166+ (defaultMakeClientRequest url r)
159− let clientEnv = (mkClientEnv mgr baseUrl) { makeClientRequest = createClientRequest }
167+ clientEnv = (mkClientEnv mgr baseUrl) { makeClientRequest = createClientRequest }
160 res <- runClientM (getRawSuccessPassHeaders HTTP.methodGet) clientEnv
168 res <- runClientM (getRawSuccessPassHeaders HTTP.methodGet) clientEnv
161 case res of
169 case res of
162 Left e ->
170 Left e ->
@@ -191,3 +199,10 @@ successSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
191 case eitherResponse of
199 case eitherResponse of
192 Left clientError -> fail $ show clientError
200 Left clientError -> fail $ show clientError
193 Right response -> matchUnion response `shouldBe` Just (WithStatus @201 carol)
201 Right response -> matchUnion response `shouldBe` Just (WithStatus @201 carol)
202+
203+ it "encodes URL pieces following ToHttpApiData instance" $ \(_, baseUrl) -> do
204+ let textOrig = "*"
205+ eitherResponse <- runClient (captureVerbatim $ Verbatim $ encodeUtf8 textOrig) baseUrl
206+ case eitherResponse of
207+ Left clientError -> fail $ show clientError
208+ Right textBack -> textBack `shouldBe` textOrig
servant-conduit/servant-conduit.cabal+7−7
@@ -1,4 +1,4 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-conduit
2name: servant-conduit
3version: 0.15.1
3version: 0.15.1
4
4
@@ -23,7 +23,7 @@ extra-source-files:
23
23
24source-repository head
24source-repository head
25 type: git
25 type: git
26− location: http://github.com/haskell-servant/servant-conduit.git
26+ location: http://github.com/haskell-servant/servant.git
27
27
28library
28library
29 exposed-modules: Servant.Conduit
29 exposed-modules: Servant.Conduit
@@ -31,9 +31,9 @@ library
31 base >=4.9 && <5
31 base >=4.9 && <5
32 , bytestring >=0.10.8.1 && <0.12
32 , bytestring >=0.10.8.1 && <0.12
33 , conduit >=1.3.1 && <1.4
33 , conduit >=1.3.1 && <1.4
34− , mtl >=2.2.2 && <2.3
34+ , mtl ^>=2.2.2 || ^>=2.3.1
35− , resourcet >=1.2.2 && <1.3
35+ , resourcet >=1.2.2 && <1.4
36− , servant >=0.15 && <0.19
36+ , servant >=0.15 && <0.20
37 , unliftio-core >=0.1.2.0 && <0.3
37 , unliftio-core >=0.1.2.0 && <0.3
38 hs-source-dirs: src
38 hs-source-dirs: src
39 default-language: Haskell2010
39 default-language: Haskell2010
@@ -54,8 +54,8 @@ test-suite example
54 , resourcet
54 , resourcet
55 , servant
55 , servant
56 , servant-conduit
56 , servant-conduit
57− , servant-server >=0.15 && <0.19
57+ , servant-server >=0.15 && <0.20
58− , servant-client >=0.15 && <0.19
58+ , servant-client >=0.15 && <0.20
59 , wai >=3.2.1.2 && <3.3
59 , wai >=3.2.1.2 && <3.3
60 , warp >=3.2.25 && <3.4
60 , warp >=3.2.25 && <3.4
61 , http-client
61 , http-client
servant-docs/CHANGELOG.md+9
@@ -1,6 +1,15 @@
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-docs/CHANGELOG.md)
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-docs/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
3
3
4+0.12
5+----
6+
7+### Significant changes
8+
9+- Generate sample cURL requests
10+ ([#1401](https://github.com/haskell-servant/servant/pull/1401/files)).
11+ Breaking change: requires sample header values to be supplied with `headers`.
12+
40.11.9
130.11.9
5------
14------
6
15
servant-docs/golden/comprehensive.md+18
@@ -530,6 +530,24 @@
530
530
531```
531```
532
532
533+## GET /resource
534+
535+### Response:
536+
537+- Status code 200
538+- Headers: []
539+
540+- Supported content types are:
541+
542+ - `application/json;charset=utf-8`
543+ - `application/json`
544+
545+- Example (`application/json;charset=utf-8`, `application/json`):
546+
547+```javascript
548+
549+```
550+
533## GET /streaming
551## GET /streaming
534
552
535### Request:
553### Request:
servant-docs/servant-docs.cabal+8−8
@@ -1,6 +1,6 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-docs
2name: servant-docs
3−version: 0.11.9
3+version: 0.12
4
4
5synopsis: generate API docs for your servant webservice
5synopsis: generate API docs for your servant webservice
6category: Servant, Web
6category: Servant, Web
@@ -41,25 +41,25 @@ library
41 --
41 --
42 -- note: mtl lower bound is so low because of GHC-7.8
42 -- note: mtl lower bound is so low because of GHC-7.8
43 build-depends:
43 build-depends:
44− base >= 4.9 && < 4.16
44+ base >= 4.9 && < 4.18
45 , bytestring >= 0.10.8.1 && < 0.12
45 , bytestring >= 0.10.8.1 && < 0.12
46− , text >= 1.2.3.0 && < 1.3
46+ , text >= 1.2.3.0 && < 2.1
47
47
48 -- Servant dependencies
48 -- Servant dependencies
49 build-depends:
49 build-depends:
50− servant >= 0.18 && <0.19
50+ servant >= 0.18 && <0.20
51
51
52 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
52 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
53 -- Here can be exceptions if we really need features from the newer versions.
53 -- Here can be exceptions if we really need features from the newer versions.
54 build-depends:
54 build-depends:
55 aeson >= 1.4.1.0 && < 3
55 aeson >= 1.4.1.0 && < 3
56 , aeson-pretty >= 0.8.5 && < 0.9
56 , aeson-pretty >= 0.8.5 && < 0.9
57− , base-compat >= 0.10.5 && < 0.12
57+ , base-compat >= 0.10.5 && < 0.13
58 , case-insensitive >= 1.2.0.11 && < 1.3
58 , case-insensitive >= 1.2.0.11 && < 1.3
59− , hashable >= 1.2.7.0 && < 1.4
59+ , hashable >= 1.2.7.0 && < 1.5
60 , http-media >= 0.7.1.3 && < 0.9
60 , http-media >= 0.7.1.3 && < 0.9
61 , http-types >= 0.12.2 && < 0.13
61 , http-types >= 0.12.2 && < 0.13
62− , lens >= 4.17 && < 5.1
62+ , lens >= 4.17 && < 5.3
63 , string-conversions >= 0.4.0.1 && < 0.5
63 , string-conversions >= 0.4.0.1 && < 0.5
64 , universe-base >= 1.1.1 && < 1.2
64 , universe-base >= 1.1.1 && < 1.2
65 , unordered-containers >= 0.2.9.0 && < 0.3
65 , unordered-containers >= 0.2.9.0 && < 0.3
servant-docs/src/Servant/Docs/Internal.hs+8−1
@@ -62,6 +62,7 @@ import GHC.TypeLits
62import Servant.API
62import Servant.API
63import Servant.API.ContentTypes
63import Servant.API.ContentTypes
64import Servant.API.TypeLevel
64import Servant.API.TypeLevel
65+import Servant.API.Generic
65
66
66import qualified Data.Universe.Helpers as U
67import qualified Data.Universe.Helpers as U
67
68
@@ -446,7 +447,7 @@ docsWith opts intros (ExtraInfo endpoints) p =
446 & apiEndpoints %~ HM.unionWith (flip combineAction) endpoints
447 & apiEndpoints %~ HM.unionWith (flip combineAction) endpoints
447
448
448
449
449−-- | Generate the docs for a given API that implements 'HasDocs' with with any
450+-- | Generate the docs for a given API that implements 'HasDocs' with any
450-- number of introduction(s)
451-- number of introduction(s)
451docsWithIntros :: HasDocs api => [DocIntro] -> Proxy api -> API
452docsWithIntros :: HasDocs api => [DocIntro] -> Proxy api -> API
452docsWithIntros intros = docsWith defaultDocOptions intros mempty
453docsWithIntros intros = docsWith defaultDocOptions intros mempty
@@ -1143,6 +1144,9 @@ instance HasDocs api => HasDocs (Vault :> api) where
1143instance HasDocs api => HasDocs (WithNamedContext name context api) where
1144instance HasDocs api => HasDocs (WithNamedContext name context api) where
1144 docsFor Proxy = docsFor (Proxy :: Proxy api)
1145 docsFor Proxy = docsFor (Proxy :: Proxy api)
1145
1146
1147+instance HasDocs api => HasDocs (WithResource res :> api) where
1148+ docsFor Proxy = docsFor (Proxy :: Proxy api)
1149+
1146instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth realm usr :> api) where
1150instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth realm usr :> api) where
1147 docsFor Proxy (endpoint, action) =
1151 docsFor Proxy (endpoint, action) =
1148 docsFor (Proxy :: Proxy api) (endpoint, action')
1152 docsFor (Proxy :: Proxy api) (endpoint, action')
@@ -1150,6 +1154,9 @@ instance (ToAuthInfo (BasicAuth realm usr), HasDocs api) => HasDocs (BasicAuth r
1150 authProxy = Proxy :: Proxy (BasicAuth realm usr)
1154 authProxy = Proxy :: Proxy (BasicAuth realm usr)
1151 action' = over authInfo (|> toAuthInfo authProxy) action
1155 action' = over authInfo (|> toAuthInfo authProxy) action
1152
1156
1157+instance HasDocs (ToServantApi api) => HasDocs (NamedRoutes api) where
1158+ docsFor Proxy = docsFor (Proxy :: Proxy (ToServantApi api))
1159+
1153-- ToSample instances for simple types
1160-- ToSample instances for simple types
1154instance ToSample NoContent
1161instance ToSample NoContent
1155instance ToSample Bool
1162instance ToSample Bool
servant-foreign/servant-foreign.cabal+8−8
@@ -1,4 +1,4 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-foreign
2name: servant-foreign
3version: 0.15.4
3version: 0.15.4
4
4
@@ -41,18 +41,18 @@ library
41 --
41 --
42 -- note: mtl lower bound is so low because of GHC-7.8
42 -- note: mtl lower bound is so low because of GHC-7.8
43 build-depends:
43 build-depends:
44− base >= 4.9 && < 4.16
44+ base >= 4.9 && < 4.18
45− , text >= 1.2.3.0 && < 1.3
45+ , text >= 1.2.3.0 && < 2.1
46
46
47 -- Servant dependencies
47 -- Servant dependencies
48 build-depends:
48 build-depends:
49− servant >=0.18 && <0.19
49+ servant >=0.18 && <0.20
50
50
51 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
51 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
52 -- Here can be exceptions if we really need features from the newer versions.
52 -- Here can be exceptions if we really need features from the newer versions.
53 build-depends:
53 build-depends:
54− base-compat >= 0.10.5 && < 0.12
54+ base-compat >= 0.10.5 && < 0.13
55− , lens >= 4.17 && < 5.1
55+ , lens >= 4.17 && < 5.3
56 , http-types >= 0.12.2 && < 0.13
56 , http-types >= 0.12.2 && < 0.13
57
57
58 hs-source-dirs: src
58 hs-source-dirs: src
@@ -74,7 +74,7 @@ test-suite spec
74
74
75 -- Additional dependencies
75 -- Additional dependencies
76 build-depends:
76 build-depends:
77− hspec >= 2.6.0 && <2.9
77+ hspec >= 2.6.0 && <2.10
78 build-tool-depends:
78 build-tool-depends:
79− hspec-discover:hspec-discover >=2.6.0 && <2.9
79+ hspec-discover:hspec-discover >=2.6.0 && <2.10
80 default-language: Haskell2010
80 default-language: Haskell2010
servant-foreign/src/Servant/Foreign/Internal.hs+7
@@ -487,6 +487,13 @@ instance HasForeign lang ftype api =>
487
487
488 foreignFor lang ftype Proxy = foreignFor lang ftype (Proxy :: Proxy api)
488 foreignFor lang ftype Proxy = foreignFor lang ftype (Proxy :: Proxy api)
489
489
490+instance HasForeign lang ftype api =>
491+ HasForeign lang ftype (WithResource res :> api) where
492+
493+ type Foreign ftype (WithResource res :> api) = Foreign ftype api
494+
495+ foreignFor lang ftype Proxy = foreignFor lang ftype (Proxy :: Proxy api)
496+
490instance HasForeign lang ftype api
497instance HasForeign lang ftype api
491 => HasForeign lang ftype (HttpVersion :> api) where
498 => HasForeign lang ftype (HttpVersion :> api) where
492 type Foreign ftype (HttpVersion :> api) = Foreign ftype api
499 type Foreign ftype (HttpVersion :> api) = Foreign ftype api
servant-http-streams/CHANGELOG.md+10
@@ -1,6 +1,16 @@
1 [The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-http-streams/CHANGELOG.md)
1 [The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-http-streams/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
3
3
4+0.18.4
5+------
6+
7+### Significant changes
8+
9+- *servant-client* / *servant-client-core* / *servant-http-streams*:
10+ Fix erroneous behavior, where only 2XX status codes would be considered
11+ successful, irrelevant of the status parameter specified by the verb
12+ combinator. ([#1469](https://github.com/haskell-servant/servant/pull/1469))
13+
40.18.3
140.18.3
5------
15------
6
16
servant-http-streams/servant-http-streams.cabal+16−16
@@ -1,6 +1,6 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-http-streams
2name: servant-http-streams
3−version: 0.18.3
3+version: 0.18.4
4
4
5synopsis: Automatic derivation of querying functions for servant
5synopsis: Automatic derivation of querying functions for servant
6category: Servant, Web
6category: Servant, Web
@@ -38,14 +38,14 @@ library
38 -- Bundled with GHC: Lower bound to not force re-installs
38 -- Bundled with GHC: Lower bound to not force re-installs
39 -- text and mtl are bundled starting with GHC-8.4
39 -- text and mtl are bundled starting with GHC-8.4
40 build-depends:
40 build-depends:
41− base >= 4.9 && < 4.16
41+ base >= 4.9 && < 4.18
42 , bytestring >= 0.10.8.1 && < 0.12
42 , bytestring >= 0.10.8.1 && < 0.12
43 , containers >= 0.5.7.1 && < 0.7
43 , containers >= 0.5.7.1 && < 0.7
44 , deepseq >= 1.4.2.0 && < 1.5
44 , deepseq >= 1.4.2.0 && < 1.5
45− , mtl >= 2.2.2 && < 2.3
45+ , mtl ^>= 2.2.2 || ^>= 2.3.1
46− , text >= 1.2.3.0 && < 1.3
46+ , text >= 1.2.3.0 && < 2.1
47− , time >= 1.6.0.1 && < 1.10
47+ , time >= 1.6.0.1 && < 1.13
48− , transformers >= 0.5.2.0 && < 0.6
48+ , transformers >= 0.5.2.0 && < 0.7
49
49
50 if !impl(ghc >= 8.2)
50 if !impl(ghc >= 8.2)
51 build-depends:
51 build-depends:
@@ -54,25 +54,25 @@ library
54 -- Servant dependencies.
54 -- Servant dependencies.
55 -- Strict dependency on `servant-client-core` as we re-export things.
55 -- Strict dependency on `servant-client-core` as we re-export things.
56 build-depends:
56 build-depends:
57− servant == 0.18.*
57+ servant >= 0.18 && < 0.20
58− , servant-client-core >= 0.18.3 && <0.18.4
58+ , servant-client-core >= 0.18.3 && <0.20
59
59
60 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
60 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
61 -- Here can be exceptions if we really need features from the newer versions.
61 -- Here can be exceptions if we really need features from the newer versions.
62 build-depends:
62 build-depends:
63− base-compat >= 0.10.5 && < 0.12
63+ base-compat >= 0.10.5 && < 0.13
64 , case-insensitive
64 , case-insensitive
65 , http-streams >= 0.8.6.1 && < 0.9
65 , http-streams >= 0.8.6.1 && < 0.9
66 , http-media >= 0.7.1.3 && < 0.9
66 , http-media >= 0.7.1.3 && < 0.9
67 , io-streams >= 1.5.0.1 && < 1.6
67 , io-streams >= 1.5.0.1 && < 1.6
68 , http-types >= 0.12.2 && < 0.13
68 , http-types >= 0.12.2 && < 0.13
69− , http-common >= 0.8.2.0 && < 0.8.3
69+ , http-common >= 0.8.2.0 && < 0.9
70 , exceptions >= 0.10.0 && < 0.11
70 , exceptions >= 0.10.0 && < 0.11
71 , kan-extensions >= 5.2 && < 5.3
71 , kan-extensions >= 5.2 && < 5.3
72 , monad-control >= 1.0.2.3 && < 1.1
72 , monad-control >= 1.0.2.3 && < 1.1
73 , semigroupoids >= 5.3.1 && < 5.4
73 , semigroupoids >= 5.3.1 && < 5.4
74 , transformers-base >= 0.4.5.2 && < 0.5
74 , transformers-base >= 0.4.5.2 && < 0.5
75− , transformers-compat >= 0.6.2 && < 0.7
75+ , transformers-compat >= 0.6.2 && < 0.8
76
76
77 hs-source-dirs: src
77 hs-source-dirs: src
78 default-language: Haskell2010
78 default-language: Haskell2010
@@ -112,16 +112,16 @@ test-suite spec
112 -- Additional dependencies
112 -- Additional dependencies
113 build-depends:
113 build-depends:
114 entropy >= 0.4.1.3 && < 0.5
114 entropy >= 0.4.1.3 && < 0.5
115− , hspec >= 2.6.0 && < 2.9
115+ , hspec >= 2.6.0 && < 2.10
116 , HUnit >= 1.6.0.0 && < 1.7
116 , HUnit >= 1.6.0.0 && < 1.7
117 , network >= 2.8.0.0 && < 3.2
117 , network >= 2.8.0.0 && < 3.2
118 , QuickCheck >= 2.12.6.1 && < 2.15
118 , QuickCheck >= 2.12.6.1 && < 2.15
119− , servant == 0.18.*
119+ , servant == 0.19.*
120− , servant-server == 0.18.*
120+ , servant-server == 0.19.*
121 , tdigest >= 0.2 && < 0.3
121 , tdigest >= 0.2 && < 0.3
122
122
123 build-tool-depends:
123 build-tool-depends:
124− hspec-discover:hspec-discover >= 2.6.0 && < 2.9
124+ hspec-discover:hspec-discover >= 2.6.0 && < 2.10
125
125
126test-suite readme
126test-suite readme
127 type: exitcode-stdio-1.0
127 type: exitcode-stdio-1.0
servant-http-streams/src/Servant/HttpStreams/Internal.hs+6−6
@@ -57,7 +57,7 @@ import GHC.Generics
57import Network.HTTP.Media
57import Network.HTTP.Media
58 (renderHeader)
58 (renderHeader)
59import Network.HTTP.Types
59import Network.HTTP.Types
60− (Status (..), hContentType, http11, renderQuery)
60+ (Status (..), hContentType, http11, renderQuery, statusIsSuccessful)
61import Servant.Client.Core
61import Servant.Client.Core
62
62
63import qualified Network.Http.Client as Client
63import qualified Network.Http.Client as Client
@@ -160,12 +160,12 @@ performRequest acceptStatus req = do
160 x <- ClientM $ lift $ lift $ Codensity $ \k -> do
160 x <- ClientM $ lift $ lift $ Codensity $ \k -> do
161 Client.sendRequest conn req' body
161 Client.sendRequest conn req' body
162 Client.receiveResponse conn $ \res' body' -> do
162 Client.receiveResponse conn $ \res' body' -> do
163− let sc = Client.getStatusCode res'
163+ let status = toEnum $ Client.getStatusCode res'
164 lbs <- BSL.fromChunks <$> Streams.toList body'
164 lbs <- BSL.fromChunks <$> Streams.toList body'
165 let res'' = clientResponseToResponse res' lbs
165 let res'' = clientResponseToResponse res' lbs
166 goodStatus = case acceptStatus of
166 goodStatus = case acceptStatus of
167− Nothing -> sc >= 200 && sc < 300
167+ Nothing -> statusIsSuccessful status
168− Just good -> sc `elem` (statusCode <$> good)
168+ Just good -> status `elem` good
169 if goodStatus
169 if goodStatus
170 then k (Right res'')
170 then k (Right res'')
171 else k (Left (mkFailureResponse burl req res''))
171 else k (Left (mkFailureResponse burl req res''))
@@ -180,8 +180,8 @@ performWithStreamingRequest req k = do
180 Client.sendRequest conn req' body
180 Client.sendRequest conn req' body
181 Client.receiveResponseRaw conn $ \res' body' -> do
181 Client.receiveResponseRaw conn $ \res' body' -> do
182 -- check status code
182 -- check status code
183− let sc = Client.getStatusCode res'
183+ let status = toEnum $ Client.getStatusCode res'
184− unless (sc >= 200 && sc < 300) $ do
184+ unless (statusIsSuccessful status) $ do
185 lbs <- BSL.fromChunks <$> Streams.toList body'
185 lbs <- BSL.fromChunks <$> Streams.toList body'
186 throwIO $ mkFailureResponse burl req (clientResponseToResponse res' lbs)
186 throwIO $ mkFailureResponse burl req (clientResponseToResponse res' lbs)
187
187
servant-machines/servant-machines.cabal+6−6
@@ -1,4 +1,4 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-machines
2name: servant-machines
3version: 0.15.1
3version: 0.15.1
4
4
@@ -23,7 +23,7 @@ extra-source-files:
23
23
24source-repository head
24source-repository head
25 type: git
25 type: git
26− location: http://github.com/haskell-servant/servant-machines.git
26+ location: http://github.com/haskell-servant/servant.git
27
27
28library
28library
29 exposed-modules: Servant.Machines
29 exposed-modules: Servant.Machines
@@ -31,8 +31,8 @@ library
31 base >=4.9 && <5
31 base >=4.9 && <5
32 , bytestring >=0.10.8.1 && <0.12
32 , bytestring >=0.10.8.1 && <0.12
33 , machines >=0.6.4 && <0.8
33 , machines >=0.6.4 && <0.8
34− , mtl >=2.2.2 && <2.3
34+ , mtl ^>=2.2.2 || ^>=2.3.1
35− , servant >=0.15 && <0.19
35+ , servant >=0.15 && <0.20
36 hs-source-dirs: src
36 hs-source-dirs: src
37 default-language: Haskell2010
37 default-language: Haskell2010
38 ghc-options: -Wall
38 ghc-options: -Wall
@@ -51,8 +51,8 @@ test-suite example
51 , servant
51 , servant
52 , machines
52 , machines
53 , servant-machines
53 , servant-machines
54− , servant-server >=0.15 && <0.19
54+ , servant-server >=0.15 && <0.20
55− , servant-client >=0.15 && <0.19
55+ , servant-client >=0.15 && <0.20
56 , wai >=3.2.1.2 && <3.3
56 , wai >=3.2.1.2 && <3.3
57 , warp >=3.2.25 && <3.4
57 , warp >=3.2.25 && <3.4
58 , http-client
58 , http-client
servant-pipes/servant-pipes.cabal+7−7
@@ -1,4 +1,4 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-pipes
2name: servant-pipes
3version: 0.15.3
3version: 0.15.3
4
4
@@ -23,7 +23,7 @@ extra-source-files:
23
23
24source-repository head
24source-repository head
25 type: git
25 type: git
26− location: http://github.com/haskell-servant/servant-pipes.git
26+ location: http://github.com/haskell-servant/servant.git
27
27
28library
28library
29 exposed-modules: Servant.Pipes
29 exposed-modules: Servant.Pipes
@@ -32,9 +32,9 @@ library
32 , bytestring >=0.10.8.1 && <0.12
32 , bytestring >=0.10.8.1 && <0.12
33 , pipes >=4.3.9 && <4.4
33 , pipes >=4.3.9 && <4.4
34 , pipes-safe >=2.3.1 && <2.4
34 , pipes-safe >=2.3.1 && <2.4
35− , mtl >=2.2.2 && <2.3
35+ , mtl ^>=2.2.2 || ^>=2.3.1
36 , monad-control >=1.0.2.3 && <1.1
36 , monad-control >=1.0.2.3 && <1.1
37− , servant >=0.15 && <0.19
37+ , servant >=0.15 && <0.20
38 hs-source-dirs: src
38 hs-source-dirs: src
39 default-language: Haskell2010
39 default-language: Haskell2010
40 ghc-options: -Wall
40 ghc-options: -Wall
@@ -51,12 +51,12 @@ test-suite example
51 , bytestring
51 , bytestring
52 , http-media
52 , http-media
53 , servant
53 , servant
54− , pipes
54+ , pipes
55 , pipes-safe
55 , pipes-safe
56 , servant-pipes
56 , servant-pipes
57 , pipes-bytestring >=2.1.6 && <2.2
57 , pipes-bytestring >=2.1.6 && <2.2
58− , servant-server >=0.15 && <0.19
58+ , servant-server >=0.15 && <0.20
59− , servant-client >=0.15 && <0.19
59+ , servant-client >=0.15 && <0.20
60 , wai >=3.2.1.2 && <3.3
60 , wai >=3.2.1.2 && <3.3
61 , warp >=3.2.25 && <3.4
61 , warp >=3.2.25 && <3.4
62 , http-client
62 , http-client
servant-server/CHANGELOG.md+30
@@ -1,6 +1,36 @@
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md)
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
3
3
4+Package versions follow the [Package Versioning Policy](https://pvp.haskell.org/): in A.B.C, bumps to either A or B represent major versions.
5+
6+0.19.2
7+------
8+
9+Compatibility with GHC 9.4, see [PR #1592](https://github.com/haskell-servant/servant/pull/1592).
10+
11+0.19.1
12+------
13+
14+- Add `MonadFail` instance for `Handler` wrt [#1545](https://github.com/haskell-servant/servant/issues/1545)
15+- Support GHC 9.2 [#1525](https://github.com/haskell-servant/servant/issues/1525)
16+- Add capture hints in `Router` type for debug and display purposes [PR #1556] (https://github.com/haskell-servant/servant/pull/1556)
17+
18+0.19
19+----
20+
21+### Significant changes
22+
23+- Drop support for GHC < 8.6.
24+- Support GHC 9.0 (GHC 9.2 should work as well, but isn't fully tested yet).
25+- Support Aeson 2 ([#1475](https://github.com/haskell-servant/servant/pull/1475)),
26+ which fixes a [DOS vulnerability](https://github.com/haskell/aeson/issues/864)
27+ related to hash collisions.
28+- Add `NamedRoutes` combinator, making support for records first-class in Servant
29+ ([#1388](https://github.com/haskell-servant/servant/pull/1388)).
30+- Add custom type errors for partially applied combinators
31+ ([#1289](https://github.com/haskell-servant/servant/pull/1289),
32+ [#1486](https://github.com/haskell-servant/servant/pull/1486)).
33+
40.18.3
340.18.3
5------
35------
6
36
servant-server/example/greet.hs−1
@@ -18,7 +18,6 @@ import Network.Wai.Handler.Warp
18
18
19import Servant
19import Servant
20import Servant.Server.Generic ()
20import Servant.Server.Generic ()
21−import Servant.API.Generic
22
21
23-- * Example
22-- * Example
24
23
servant-server/servant-server.cabal+14−14
@@ -1,6 +1,6 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant-server
2name: servant-server
3−version: 0.18.3
3+version: 0.19.2
4
4
5synopsis: A family of combinators for defining webservices APIs and serving them
5synopsis: A family of combinators for defining webservices APIs and serving them
6category: Servant, Web
6category: Servant, Web
@@ -23,7 +23,7 @@ author: Servant Contributors
23maintainer: haskell-servant-maintainers@googlegroups.com
23maintainer: haskell-servant-maintainers@googlegroups.com
24copyright: 2014-2016 Zalora South East Asia Pte Ltd, 2016-2019 Servant Contributors
24copyright: 2014-2016 Zalora South East Asia Pte Ltd, 2016-2019 Servant Contributors
25build-type: Simple
25build-type: Simple
26−tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.2 || ==9.0.1
26+tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.3
27
27
28extra-source-files:
28extra-source-files:
29 CHANGELOG.md
29 CHANGELOG.md
@@ -60,25 +60,25 @@ library
60 -- Bundled with GHC: Lower bound to not force re-installs
60 -- Bundled with GHC: Lower bound to not force re-installs
61 -- text and mtl are bundled starting with GHC-8.4
61 -- text and mtl are bundled starting with GHC-8.4
62 build-depends:
62 build-depends:
63− base >= 4.9 && < 4.16
63+ base >= 4.9 && < 4.18
64 , bytestring >= 0.10.8.1 && < 0.12
64 , bytestring >= 0.10.8.1 && < 0.12
65 , constraints >= 0.2 && < 0.14
65 , constraints >= 0.2 && < 0.14
66 , containers >= 0.5.7.1 && < 0.7
66 , containers >= 0.5.7.1 && < 0.7
67− , mtl >= 2.2.2 && < 2.3
67+ , mtl ^>= 2.2.2 || ^>= 2.3.1
68− , text >= 1.2.3.0 && < 1.3
68+ , text >= 1.2.3.0 && < 2.1
69− , transformers >= 0.5.2.0 && < 0.6
69+ , transformers >= 0.5.2.0 && < 0.7
70 , filepath >= 1.4.1.1 && < 1.5
70 , filepath >= 1.4.1.1 && < 1.5
71
71
72 -- Servant dependencies
72 -- Servant dependencies
73 -- strict dependency as we re-export 'servant' things.
73 -- strict dependency as we re-export 'servant' things.
74 build-depends:
74 build-depends:
75− servant >= 0.18.3 && < 0.18.4
75+ servant >= 0.19 && < 0.20
76− , http-api-data >= 0.4.1 && < 0.4.4
76+ , http-api-data >= 0.4.1 && < 0.5.1
77
77
78 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
78 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
79 -- Here can be exceptions if we really need features from the newer versions.
79 -- Here can be exceptions if we really need features from the newer versions.
80 build-depends:
80 build-depends:
81− base-compat >= 0.10.5 && < 0.12
81+ base-compat >= 0.10.5 && < 0.13
82 , base64-bytestring >= 1.0.0.1 && < 1.3
82 , base64-bytestring >= 1.0.0.1 && < 1.3
83 , exceptions >= 0.10.0 && < 0.11
83 , exceptions >= 0.10.0 && < 0.11
84 , http-media >= 0.7.1.3 && < 0.9
84 , http-media >= 0.7.1.3 && < 0.9
@@ -88,10 +88,10 @@ library
88 , network >= 2.8 && < 3.2
88 , network >= 2.8 && < 3.2
89 , sop-core >= 0.4.0.0 && < 0.6
89 , sop-core >= 0.4.0.0 && < 0.6
90 , string-conversions >= 0.4.0.1 && < 0.5
90 , string-conversions >= 0.4.0.1 && < 0.5
91− , resourcet >= 1.2.2 && < 1.3
91+ , resourcet >= 1.2.2 && < 1.4
92 , tagged >= 0.8.6 && < 0.9
92 , tagged >= 0.8.6 && < 0.9
93 , transformers-base >= 0.4.5.2 && < 0.5
93 , transformers-base >= 0.4.5.2 && < 0.5
94− , wai >= 3.2.1.2 && < 3.3
94+ , wai >= 3.2.2.1 && < 3.3
95 , wai-app-static >= 3.1.6.2 && < 3.2
95 , wai-app-static >= 3.1.6.2 && < 3.2
96 , word8 >= 0.1.3 && < 0.2
96 , word8 >= 0.1.3 && < 0.2
97
97
@@ -159,7 +159,7 @@ test-suite spec
159 build-depends:
159 build-depends:
160 aeson >= 1.4.1.0 && < 3
160 aeson >= 1.4.1.0 && < 3
161 , directory >= 1.3.0.0 && < 1.4
161 , directory >= 1.3.0.0 && < 1.4
162− , hspec >= 2.6.0 && < 2.9
162+ , hspec >= 2.6.0 && < 2.10
163 , hspec-wai >= 0.10.1 && < 0.12
163 , hspec-wai >= 0.10.1 && < 0.12
164 , QuickCheck >= 2.12.6.1 && < 2.15
164 , QuickCheck >= 2.12.6.1 && < 2.15
165 , should-not-typecheck >= 2.1.0 && < 2.2
165 , should-not-typecheck >= 2.1.0 && < 2.2
@@ -167,4 +167,4 @@ test-suite spec
167 , wai-extra >= 3.0.24.3 && < 3.2
167 , wai-extra >= 3.0.24.3 && < 3.2
168
168
169 build-tool-depends:
169 build-tool-depends:
170− hspec-discover:hspec-discover >= 2.6.0 && <2.9
170+ hspec-discover:hspec-discover >= 2.6.0 && <2.10
servant-server/src/Servant/Server.hs+3−2
@@ -235,7 +235,7 @@ hoistServer p = hoistServerWithContext p (Proxy :: Proxy '[])
235-- > │ └─ e/
235-- > │ └─ e/
236-- > │ └─•
236-- > │ └─•
237-- > ├─ b/
237-- > ├─ b/
238−-- > │ └─ <capture>/
238+-- > │ └─ <x::Int>/
239-- > │ ├─•
239-- > │ ├─•
240-- > │ ┆
240-- > │ ┆
241-- > │ └─•
241-- > │ └─•
@@ -252,7 +252,8 @@ hoistServer p = hoistServerWithContext p (Proxy :: Proxy '[])
252--
252--
253-- [@─•@] Leaves reflect endpoints.
253-- [@─•@] Leaves reflect endpoints.
254--
254--
255−-- [@\<capture\>/@] This is a delayed capture of a path component.
255+-- [@\<x::Int\>/@] This is a delayed capture of a single
256+-- path component named @x@, of expected type @Int@.
256--
257--
257-- [@\<raw\>@] This is a part of the API we do not know anything about.
258-- [@\<raw\>@] This is a part of the API we do not know anything about.
258--
259--
servant-server/src/Servant/Server/Experimental/Auth.hs+2−1
@@ -1,5 +1,6 @@
1{-# OPTIONS_GHC -fno-warn-orphans #-}
1{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE FlexibleInstances #-}
@@ -44,7 +45,7 @@ type family AuthServerData a :: *
44-- NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
45-- NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
45newtype AuthHandler r usr = AuthHandler
46newtype AuthHandler r usr = AuthHandler
46 { unAuthHandler :: r -> Handler usr }
47 { unAuthHandler :: r -> Handler usr }
47− deriving (Generic, Typeable)
48+ deriving (Functor, Generic, Typeable)
48
49
49-- | NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
50-- | NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
50mkAuthHandler :: (r -> Handler usr) -> AuthHandler r usr
51mkAuthHandler :: (r -> Handler usr) -> AuthHandler r usr
servant-server/src/Servant/Server/Internal.hs+117−44
@@ -1,10 +1,12 @@
1{-# LANGUAGE AllowAmbiguousTypes #-}
1{-# LANGUAGE AllowAmbiguousTypes #-}
2{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE CPP #-}
3{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE InstanceSigs #-}
8{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE LambdaCase #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE MultiParamTypeClasses #-}
9{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE OverloadedStrings #-}
10{-# LANGUAGE PolyKinds #-}
12{-# LANGUAGE PolyKinds #-}
@@ -34,14 +36,15 @@ module Servant.Server.Internal
34import Control.Monad
36import Control.Monad
35 (join, when)
37 (join, when)
36import Control.Monad.Trans
38import Control.Monad.Trans
37− (liftIO)
39+ (liftIO, lift)
38import Control.Monad.Trans.Resource
40import Control.Monad.Trans.Resource
39− (runResourceT)
41+ (runResourceT, ReleaseKey)
42+import Data.Acquire
40import qualified Data.ByteString as B
43import qualified Data.ByteString as B
41import qualified Data.ByteString.Builder as BB
44import qualified Data.ByteString.Builder as BB
42import qualified Data.ByteString.Char8 as BC8
45import qualified Data.ByteString.Char8 as BC8
43import qualified Data.ByteString.Lazy as BL
46import qualified Data.ByteString.Lazy as BL
44−import Data.Constraint (Dict(..))
47+import Data.Constraint (Constraint, Dict(..))
45import Data.Either
48import Data.Either
46 (partitionEithers)
49 (partitionEithers)
47import Data.Maybe
50import Data.Maybe
@@ -56,14 +59,14 @@ import qualified Data.Text as T
56import Data.Typeable
59import Data.Typeable
57import GHC.Generics
60import GHC.Generics
58import GHC.TypeLits
61import GHC.TypeLits
59− (KnownNat, KnownSymbol, natVal, symbolVal)
62+ (KnownNat, KnownSymbol, TypeError, symbolVal)
60import qualified Network.HTTP.Media as NHM
63import qualified Network.HTTP.Media as NHM
61import Network.HTTP.Types hiding
64import Network.HTTP.Types hiding
62 (Header, ResponseHeaders)
65 (Header, ResponseHeaders)
63import Network.Socket
66import Network.Socket
64 (SockAddr)
67 (SockAddr)
65import Network.Wai
68import Network.Wai
66− (Application, Request, httpVersion, isSecure, lazyRequestBody,
69+ (Application, Request, Response, ResponseReceived, httpVersion, isSecure, lazyRequestBody,
67 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
70 queryString, remoteHost, getRequestBodyChunk, requestHeaders,
68 requestMethod, responseLBS, responseStream, vault)
71 requestMethod, responseLBS, responseStream, vault)
69import Prelude ()
72import Prelude ()
@@ -73,10 +76,10 @@ import Servant.API
73 CaptureAll, Description, EmptyAPI, Fragment,
76 CaptureAll, Description, EmptyAPI, Fragment,
74 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
77 FramingRender (..), FramingUnrender (..), FromSourceIO (..),
75 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
78 Header', If, IsSecure (..), NoContentVerb, QueryFlag,
76− QueryParam', QueryParams, Raw, ReflectMethod (reflectMethod),
79+ QueryParam', QueryParams, Raw, RawM, ReflectMethod (reflectMethod),
77 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
80 RemoteHost, ReqBody', SBool (..), SBoolI (..), SourceIO,
78 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
81 Stream, StreamBody', Summary, ToSourceIO (..), Vault, Verb,
79− WithNamedContext, NamedRoutes)
82+ WithNamedContext, WithResource, NamedRoutes)
80import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
83import Servant.API.Generic (GenericMode(..), ToServant, ToServantApi, GServantProduct, toServant, fromServant)
81import Servant.API.ContentTypes
84import Servant.API.ContentTypes
82 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
85 (AcceptHeader (..), AllCTRender (..), AllCTUnrender (..),
@@ -87,10 +90,15 @@ import Servant.API.Modifiers
87 unfoldRequestArgument)
90 unfoldRequestArgument)
88import Servant.API.ResponseHeaders
91import Servant.API.ResponseHeaders
89 (GetHeaders, Headers, getHeaders, getResponse)
92 (GetHeaders, Headers, getHeaders, getResponse)
93+import Servant.API.Status
94+ (statusFromNat)
90import qualified Servant.Types.SourceT as S
95import qualified Servant.Types.SourceT as S
96+import Servant.API.TypeErrors
91import Web.HttpApiData
97import Web.HttpApiData
92 (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
98 (FromHttpApiData, parseHeader, parseQueryParam, parseUrlPiece,
93 parseUrlPieces)
99 parseUrlPieces)
100+import Data.Kind
101+ (Type)
94
102
95import Servant.Server.Internal.BasicAuth
103import Servant.Server.Internal.BasicAuth
96import Servant.Server.Internal.Context
104import Servant.Server.Internal.Context
@@ -109,6 +117,10 @@ import Servant.API.TypeLevel
109 (AtLeastOneFragment, FragmentUnique)
117 (AtLeastOneFragment, FragmentUnique)
110
118
111class HasServer api context where
119class HasServer api context where
120+ -- | The type of a server for this API, given a monad to run effects in.
121+ --
122+ -- Note that the result kind is @*@, so it is /not/ a monad transformer, unlike
123+ -- what the @T@ in the name might suggest.
112 type ServerT api (m :: * -> *) :: *
124 type ServerT api (m :: * -> *) :: *
113
125
114 route ::
126 route ::
@@ -170,7 +182,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
170-- > server = getBook
182-- > server = getBook
171-- > where getBook :: Text -> Handler Book
183-- > where getBook :: Text -> Handler Book
172-- > getBook isbn = ...
184-- > getBook isbn = ...
173−instance (KnownSymbol capture, FromHttpApiData a
185+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
174 , HasServer api context, SBoolI (FoldLenient mods)
186 , HasServer api context, SBoolI (FoldLenient mods)
175 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
187 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
176 )
188 )
@@ -182,7 +194,7 @@ instance (KnownSymbol capture, FromHttpApiData a
182 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
194 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
183
195
184 route Proxy context d =
196 route Proxy context d =
185− CaptureRouter $
197+ CaptureRouter [hint] $
186 route (Proxy :: Proxy api)
198 route (Proxy :: Proxy api)
187 context
199 context
188 (addCapture d $ \ txt -> withRequest $ \ request ->
200 (addCapture d $ \ txt -> withRequest $ \ request ->
@@ -194,6 +206,7 @@ instance (KnownSymbol capture, FromHttpApiData a
194 where
206 where
195 rep = typeRep (Proxy :: Proxy Capture')
207 rep = typeRep (Proxy :: Proxy Capture')
196 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
208 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
209+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy a))
197
210
198-- | If you use 'CaptureAll' in one of the endpoints for your API,
211-- | If you use 'CaptureAll' in one of the endpoints for your API,
199-- this automatically requires your server-side handler to be a
212-- this automatically requires your server-side handler to be a
@@ -212,7 +225,7 @@ instance (KnownSymbol capture, FromHttpApiData a
212-- > server = getSourceFile
225-- > server = getSourceFile
213-- > where getSourceFile :: [Text] -> Handler Book
226-- > where getSourceFile :: [Text] -> Handler Book
214-- > getSourceFile pathSegments = ...
227-- > getSourceFile pathSegments = ...
215−instance (KnownSymbol capture, FromHttpApiData a
228+instance (KnownSymbol capture, FromHttpApiData a, Typeable a
216 , HasServer api context
229 , HasServer api context
217 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
230 , HasContextEntry (MkContextWithErrorFormatter context) ErrorFormatters
218 )
231 )
@@ -224,7 +237,7 @@ instance (KnownSymbol capture, FromHttpApiData a
224 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
237 hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s
225
238
226 route Proxy context d =
239 route Proxy context d =
227− CaptureAllRouter $
240+ CaptureAllRouter [hint] $
228 route (Proxy :: Proxy api)
241 route (Proxy :: Proxy api)
229 context
242 context
230 (addCapture d $ \ txts -> withRequest $ \ request ->
243 (addCapture d $ \ txts -> withRequest $ \ request ->
@@ -235,6 +248,43 @@ instance (KnownSymbol capture, FromHttpApiData a
235 where
248 where
236 rep = typeRep (Proxy :: Proxy CaptureAll)
249 rep = typeRep (Proxy :: Proxy CaptureAll)
237 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
250 formatError = urlParseErrorFormatter $ getContextEntry (mkContextWithErrorFormatter context)
251+ hint = CaptureHint (T.pack $ symbolVal $ Proxy @capture) (typeRep (Proxy :: Proxy [a]))
252+
253+-- | If you use 'WithResource' in one of the endpoints for your API Servant
254+-- will provide the handler for this endpoint an argument of the specified type.
255+-- The lifespan of this resource will be automatically managed by Servant. This
256+-- resource will be created before the handler starts and it will be destoyed
257+-- after it ends. A new resource is created for each request to the endpoint.
258+
259+-- The creation and destruction are done using a 'Data.Acquire.Acquire'
260+-- provided via server 'Context'.
261+--
262+-- Example
263+--
264+-- > type MyApi = WithResource Handle :> "writeToFile" :> Post '[JSON] NoContent
265+-- >
266+-- > server :: Server MyApi
267+-- > server = writeToFile
268+-- > where writeToFile :: (ReleaseKey, Handle) -> Handler NoContent
269+-- > writeToFile (_, h) = hPutStrLn h "message"
270+--
271+-- In addition to the resource, the handler will also receive a 'ReleaseKey'
272+-- which can be used to deallocate the resource before the end of the request
273+-- if desired.
274+
275+instance (HasServer api ctx, HasContextEntry ctx (Acquire a))
276+ => HasServer (WithResource a :> api) ctx where
277+
278+ type ServerT (WithResource a :> api) m = (ReleaseKey, a) -> ServerT api m
279+
280+ hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy @api) pc nt . s
281+
282+ route Proxy context d = route (Proxy @api) context (d `addParameterCheck` allocateResource)
283+ where
284+ allocateResource :: DelayedIO (ReleaseKey, a)
285+ allocateResource = DelayedIO $ lift $ allocateAcquire (getContextEntry context)
286+
287+
238
288
239allowedMethodHead :: Method -> Request -> Bool
289allowedMethodHead :: Method -> Request -> Bool
240allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
290allowedMethodHead method request = method == methodGet && requestMethod request == methodHead
@@ -298,7 +348,7 @@ instance {-# OVERLAPPABLE #-}
298
348
299 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
349 route Proxy _ = methodRouter ([],) method (Proxy :: Proxy ctypes) status
300 where method = reflectMethod (Proxy :: Proxy method)
350 where method = reflectMethod (Proxy :: Proxy method)
301− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
351+ status = statusFromNat (Proxy :: Proxy status)
302
352
303instance {-# OVERLAPPING #-}
353instance {-# OVERLAPPING #-}
304 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
354 ( AllCTRender ctypes a, ReflectMethod method, KnownNat status
@@ -310,7 +360,7 @@ instance {-# OVERLAPPING #-}
310
360
311 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
361 route Proxy _ = methodRouter (\x -> (getHeaders x, getResponse x)) method (Proxy :: Proxy ctypes) status
312 where method = reflectMethod (Proxy :: Proxy method)
362 where method = reflectMethod (Proxy :: Proxy method)
313− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
363+ status = statusFromNat (Proxy :: Proxy status)
314
364
315instance (ReflectMethod method) =>
365instance (ReflectMethod method) =>
316 HasServer (NoContentVerb method) context where
366 HasServer (NoContentVerb method) context where
@@ -331,7 +381,7 @@ instance {-# OVERLAPPABLE #-}
331
381
332 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
382 route Proxy _ = streamRouter ([],) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
333 where method = reflectMethod (Proxy :: Proxy method)
383 where method = reflectMethod (Proxy :: Proxy method)
334− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
384+ status = statusFromNat (Proxy :: Proxy status)
335
385
336
386
337instance {-# OVERLAPPING #-}
387instance {-# OVERLAPPING #-}
@@ -345,7 +395,7 @@ instance {-# OVERLAPPING #-}
345
395
346 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
396 route Proxy _ = streamRouter (\x -> (getHeaders x, getResponse x)) method status (Proxy :: Proxy framing) (Proxy :: Proxy ctype)
347 where method = reflectMethod (Proxy :: Proxy method)
397 where method = reflectMethod (Proxy :: Proxy method)
348− status = toEnum . fromInteger $ natVal (Proxy :: Proxy status)
398+ status = statusFromNat (Proxy :: Proxy status)
349
399
350
400
351streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
401streamRouter :: forall ctype a c chunk env framing. (MimeRender ctype chunk, FramingRender framing, ToSourceIO chunk a) =>
@@ -603,6 +653,35 @@ instance HasServer Raw context where
603 Fail a -> respond $ Fail a
653 Fail a -> respond $ Fail a
604 FailFatal e -> respond $ FailFatal e
654 FailFatal e -> respond $ FailFatal e
605
655
656+-- | Just pass the request to the underlying application and serve its response.
657+--
658+-- Example:
659+--
660+-- > type MyApi = "images" :> Raw
661+-- >
662+-- > server :: Server MyApi
663+-- > server = serveDirectory "/var/www/images"
664+instance HasServer RawM context where
665+ type ServerT RawM m = Request -> (Response -> IO ResponseReceived) -> m ResponseReceived
666+
667+ route
668+ :: Proxy RawM
669+ -> Context context
670+ -> Delayed env (Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived) -> Router env
671+ route _ _ handleDelayed = RawRouter $ \env request respond -> runResourceT $ do
672+ routeResult <- runDelayed handleDelayed env request
673+ let respond' = liftIO . respond
674+ liftIO $ case routeResult of
675+ Route handler -> runHandler (handler request (respond . Route)) >>=
676+ \case
677+ Left e -> respond' $ FailFatal e
678+ Right a -> pure a
679+ Fail e -> respond' $ Fail e
680+ FailFatal e -> respond' $ FailFatal e
681+
682+ hoistServerWithContext _ _ f srvM = \req respond -> f (srvM req respond)
683+
684+
606-- | If you use 'ReqBody' in one of the endpoints for your API,
685-- | If you use 'ReqBody' in one of the endpoints for your API,
607-- this automatically requires your server-side handler to be a function
686-- this automatically requires your server-side handler to be a function
608-- that takes an argument of the type specified by 'ReqBody'.
687-- that takes an argument of the type specified by 'ReqBody'.
@@ -812,38 +891,19 @@ instance (HasContextEntry context (NamedContext name subContext), HasServer subA
812 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
891 hoistServerWithContext _ _ nt s = hoistServerWithContext (Proxy :: Proxy subApi) (Proxy :: Proxy subContext) nt s
813
892
814-------------------------------------------------------------------------------
893-------------------------------------------------------------------------------
815−-- TypeError helpers
894+-- Custom type errors
816-------------------------------------------------------------------------------
895-------------------------------------------------------------------------------
817
896
818−-- | This instance catches mistakes when there are non-saturated
897+-- Erroring instance for 'HasServer' when a combinator is not fully applied
819−-- type applications on LHS of ':>'.
898+instance TypeError (PartialApplication
820−--
899+#if __GLASGOW_HASKELL__ >= 904
821−-- >>> serve (Proxy :: Proxy (Capture "foo" :> Get '[JSON] Int)) (error "...")
900+ @(Type -> [Type] -> Constraint)
822−-- ...
901+#endif
823−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
824−-- ...Maybe you haven't applied enough arguments to
825−-- ...Capture' '[] "foo"
826−-- ...
827−--
828−-- >>> undefined :: Server (Capture "foo" :> Get '[JSON] Int)
829−-- ...
830−-- ...Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'.
831−-- ...Maybe you haven't applied enough arguments to
832−-- ...Capture' '[] "foo"
833−-- ...
834−--
835−instance TypeError (HasServerArrowKindError arr) => HasServer ((arr :: k -> l) :> api) context
902+ HasServer arr) => HasServer ((arr :: a -> b) :> sub) context
836 where
903 where
837− type ServerT (arr :> api) m = TypeError (HasServerArrowKindError arr)
904+ type ServerT (arr :> sub) _ = TypeError (PartialApplication (HasServer :: * -> [*] -> Constraint) arr)
838− -- it doesn't really matter what sub route we peak
905+ route = error "unreachable"
839− route _ _ _ = error "servant-server panic: impossible happened in HasServer (arr :> api)"
840− hoistServerWithContext _ _ _ = id
906+ hoistServerWithContext _ _ _ _ = error "unreachable"
841−
842−-- Cannot have TypeError here, otherwise use of this symbol will error :)
843−type HasServerArrowKindError arr =
844− 'Text "Expected something of kind Symbol or *, got: k -> l on the LHS of ':>'."
845− ':$$: 'Text "Maybe you haven't applied enough arguments to"
846− ':$$: 'ShowType arr
847
907
848-- | This instance prevents from accidentally using '->' instead of ':>'
908-- | This instance prevents from accidentally using '->' instead of ':>'
849--
909--
@@ -878,6 +938,19 @@ type HasServerArrowTypeError a b =
878 ':$$: 'Text "and"
938 ':$$: 'Text "and"
879 ':$$: 'ShowType b
939 ':$$: 'ShowType b
880
940
941+-- Erroring instances for 'HasServer' for unknown API combinators
942+
943+-- XXX: This omits the @context@ parameter, e.g.:
944+--
945+-- "There is no instance for HasServer (Bool :> …)". Do we care ?
946+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub
947+#if __GLASGOW_HASKELL__ >= 904
948+ @(Type -> [Type] -> Constraint)
949+#endif
950+ HasServer ty) => HasServer (ty :> sub) context
951+
952+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasServer api context)) => HasServer api context
953+
881-- | Ignore @'Fragment'@ in server handlers.
954-- | Ignore @'Fragment'@ in server handlers.
882-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
955-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
883--
956--
servant-server/src/Servant/Server/Internal/Handler.hs+7−2
@@ -13,17 +13,19 @@ import Control.Monad.Base
13import Control.Monad.Catch
13import Control.Monad.Catch
14 (MonadCatch, MonadMask, MonadThrow)
14 (MonadCatch, MonadMask, MonadThrow)
15import Control.Monad.Error.Class
15import Control.Monad.Error.Class
16− (MonadError)
16+ (MonadError, throwError)
17import Control.Monad.IO.Class
17import Control.Monad.IO.Class
18 (MonadIO)
18 (MonadIO)
19import Control.Monad.Trans.Control
19import Control.Monad.Trans.Control
20 (MonadBaseControl (..))
20 (MonadBaseControl (..))
21import Control.Monad.Trans.Except
21import Control.Monad.Trans.Except
22 (ExceptT, runExceptT)
22 (ExceptT, runExceptT)
23+import Data.String
24+ (fromString)
23import GHC.Generics
25import GHC.Generics
24 (Generic)
26 (Generic)
25import Servant.Server.Internal.ServerError
27import Servant.Server.Internal.ServerError
26− (ServerError)
28+ (ServerError, errBody, err500)
27
29
28newtype Handler a = Handler { runHandler' :: ExceptT ServerError IO a }
30newtype Handler a = Handler { runHandler' :: ExceptT ServerError IO a }
29 deriving
31 deriving
@@ -32,6 +34,9 @@ newtype Handler a = Handler { runHandler' :: ExceptT ServerError IO a }
32 , MonadThrow, MonadCatch, MonadMask
34 , MonadThrow, MonadCatch, MonadMask
33 )
35 )
34
36
37+instance MonadFail Handler where
38+ fail str = throwError err500 { errBody = fromString str }
39+
35instance MonadBase IO Handler where
40instance MonadBase IO Handler where
36 liftBase = Handler . liftBase
41 liftBase = Handler . liftBase
37
42
servant-server/src/Servant/Server/Internal/Router.hs+50−15
@@ -9,12 +9,16 @@ import Prelude.Compat
9
9
10import Data.Function
10import Data.Function
11 (on)
11 (on)
12+import Data.List
13+ (nub)
12import Data.Map
14import Data.Map
13 (Map)
15 (Map)
14import qualified Data.Map as M
16import qualified Data.Map as M
15import Data.Text
17import Data.Text
16 (Text)
18 (Text)
17import qualified Data.Text as T
19import qualified Data.Text as T
20+import Data.Typeable
21+ (TypeRep)
18import Network.Wai
22import Network.Wai
19 (Response, pathInfo)
23 (Response, pathInfo)
20import Servant.Server.Internal.ErrorFormatter
24import Servant.Server.Internal.ErrorFormatter
@@ -24,6 +28,21 @@ import Servant.Server.Internal.ServerError
24
28
25type Router env = Router' env RoutingApplication
29type Router env = Router' env RoutingApplication
26
30
31+-- | Holds information about pieces of url that are captured as variables.
32+data CaptureHint = CaptureHint
33+ { captureName :: Text
34+ -- ^ Holds the name of the captured variable
35+ , captureType :: TypeRep
36+ -- ^ Holds the type of the captured variable
37+ }
38+ deriving (Show, Eq)
39+
40+toCaptureTag :: CaptureHint -> Text
41+toCaptureTag hint = captureName hint <> "::" <> (T.pack . show) (captureType hint)
42+
43+toCaptureTags :: [CaptureHint] -> Text
44+toCaptureTags hints = "<" <> T.intercalate "|" (map toCaptureTag hints) <> ">"
45+
27-- | Internal representation of a router.
46-- | Internal representation of a router.
28--
47--
29-- The first argument describes an environment type that is
48-- The first argument describes an environment type that is
@@ -36,12 +55,23 @@ data Router' env a =
36 -- ^ the map contains routers for subpaths (first path component used
55 -- ^ the map contains routers for subpaths (first path component used
37 -- for lookup and removed afterwards), the list contains handlers
56 -- for lookup and removed afterwards), the list contains handlers
38 -- for the empty path, to be tried in order
57 -- for the empty path, to be tried in order
39− | CaptureRouter (Router' (Text, env) a)
58+ | CaptureRouter [CaptureHint] (Router' (Text, env) a)
40 -- ^ first path component is passed to the child router in its
59 -- ^ first path component is passed to the child router in its
41− -- environment and removed afterwards
60+ -- environment and removed afterwards.
61+ -- The first argument is a list of hints for all variables that can be
62+ -- captured by the router. The fact that it is a list is counter-intuitive,
63+ -- because the 'Capture' combinator only allows to capture a single varible,
64+ -- with a single name and a single type. However, the 'choice' smart
65+ -- constructor may merge two 'Capture' combinators with different hints, thus
66+ -- forcing the type to be '[CaptureHint]'.
67+ -- Because 'CaptureRouter' is built from a 'Capture' combinator, the list of
68+ -- hints should always be non-empty.
42− | CaptureAllRouter (Router' ([Text], env) a)
69+ | CaptureAllRouter [CaptureHint] (Router' ([Text], env) a)
43 -- ^ all path components are passed to the child router in its
70 -- ^ all path components are passed to the child router in its
44 -- environment and are removed afterwards
71 -- environment and are removed afterwards
72+ -- The first argument is a hint for the list of variables that can be
73+ -- captured by the router. Note that the 'captureType' field of the hint
74+ -- should always be '[a]' for some 'a'.
45 | RawRouter (env -> a)
75 | RawRouter (env -> a)
46 -- ^ to be used for routes we do not know anything about
76 -- ^ to be used for routes we do not know anything about
47 | Choice (Router' env a) (Router' env a)
77 | Choice (Router' env a) (Router' env a)
@@ -69,8 +99,8 @@ leafRouter l = StaticRouter M.empty [l]
69choice :: Router' env a -> Router' env a -> Router' env a
99choice :: Router' env a -> Router' env a -> Router' env a
70choice (StaticRouter table1 ls1) (StaticRouter table2 ls2) =
100choice (StaticRouter table1 ls1) (StaticRouter table2 ls2) =
71 StaticRouter (M.unionWith choice table1 table2) (ls1 ++ ls2)
101 StaticRouter (M.unionWith choice table1 table2) (ls1 ++ ls2)
72−choice (CaptureRouter router1) (CaptureRouter router2) =
102+choice (CaptureRouter hints1 router1) (CaptureRouter hints2 router2) =
73− CaptureRouter (choice router1 router2)
103+ CaptureRouter (nub $ hints1 ++ hints2) (choice router1 router2)
74choice router1 (Choice router2 router3) = Choice (choice router1 router2) router3
104choice router1 (Choice router2 router3) = Choice (choice router1 router2) router3
75choice router1 router2 = Choice router1 router2
105choice router1 router2 = Choice router1 router2
76
106
@@ -84,7 +114,11 @@ choice router1 router2 = Choice router1 router2
84--
114--
85data RouterStructure =
115data RouterStructure =
86 StaticRouterStructure (Map Text RouterStructure) Int
116 StaticRouterStructure (Map Text RouterStructure) Int
87− | CaptureRouterStructure RouterStructure
117+ | CaptureRouterStructure [CaptureHint] RouterStructure
118+ -- ^ The first argument holds information about variables
119+ -- that are captured by the router. There may be several hints
120+ -- if several routers have been aggregated by the 'choice'
121+ -- smart constructor.
88 | RawRouterStructure
122 | RawRouterStructure
89 | ChoiceStructure RouterStructure RouterStructure
123 | ChoiceStructure RouterStructure RouterStructure
90 deriving (Eq, Show)
124 deriving (Eq, Show)
@@ -98,11 +132,11 @@ data RouterStructure =
98routerStructure :: Router' env a -> RouterStructure
132routerStructure :: Router' env a -> RouterStructure
99routerStructure (StaticRouter m ls) =
133routerStructure (StaticRouter m ls) =
100 StaticRouterStructure (fmap routerStructure m) (length ls)
134 StaticRouterStructure (fmap routerStructure m) (length ls)
101−routerStructure (CaptureRouter router) =
135+routerStructure (CaptureRouter hints router) =
102− CaptureRouterStructure $
136+ CaptureRouterStructure hints $
103 routerStructure router
137 routerStructure router
104−routerStructure (CaptureAllRouter router) =
138+routerStructure (CaptureAllRouter hints router) =
105− CaptureRouterStructure $
139+ CaptureRouterStructure hints $
106 routerStructure router
140 routerStructure router
107routerStructure (RawRouter _) =
141routerStructure (RawRouter _) =
108 RawRouterStructure
142 RawRouterStructure
@@ -114,8 +148,8 @@ routerStructure (Choice r1 r2) =
114-- | Compare the structure of two routers.
148-- | Compare the structure of two routers.
115--
149--
116sameStructure :: Router' env a -> Router' env b -> Bool
150sameStructure :: Router' env a -> Router' env b -> Bool
117−sameStructure r1 r2 =
151+sameStructure router1 router2 =
118− routerStructure r1 == routerStructure r2
152+ routerStructure router1 == routerStructure router2
119
153
120-- | Provide a textual representation of the
154-- | Provide a textual representation of the
121-- structure of a router.
155-- structure of a router.
@@ -126,7 +160,8 @@ routerLayout router =
126 where
160 where
127 mkRouterLayout :: Bool -> RouterStructure -> [Text]
161 mkRouterLayout :: Bool -> RouterStructure -> [Text]
128 mkRouterLayout c (StaticRouterStructure m n) = mkSubTrees c (M.toList m) n
162 mkRouterLayout c (StaticRouterStructure m n) = mkSubTrees c (M.toList m) n
129− mkRouterLayout c (CaptureRouterStructure r) = mkSubTree c "<capture>" (mkRouterLayout False r)
163+ mkRouterLayout c (CaptureRouterStructure hints r) =
164+ mkSubTree c (toCaptureTags hints) (mkRouterLayout False r)
130 mkRouterLayout c RawRouterStructure =
165 mkRouterLayout c RawRouterStructure =
131 if c then ["├─ <raw>"] else ["└─ <raw>"]
166 if c then ["├─ <raw>"] else ["└─ <raw>"]
132 mkRouterLayout c (ChoiceStructure r1 r2) =
167 mkRouterLayout c (ChoiceStructure r1 r2) =
@@ -169,7 +204,7 @@ runRouterEnv fmt router env request respond =
169 -> let request' = request { pathInfo = rest }
204 -> let request' = request { pathInfo = rest }
170 in runRouterEnv fmt router' env request' respond
205 in runRouterEnv fmt router' env request' respond
171 _ -> respond $ Fail $ fmt request
206 _ -> respond $ Fail $ fmt request
172− CaptureRouter router' ->
207+ CaptureRouter _ router' ->
173 case pathInfo request of
208 case pathInfo request of
174 [] -> respond $ Fail $ fmt request
209 [] -> respond $ Fail $ fmt request
175 -- This case is to handle trailing slashes.
210 -- This case is to handle trailing slashes.
@@ -177,7 +212,7 @@ runRouterEnv fmt router env request respond =
177 first : rest
212 first : rest
178 -> let request' = request { pathInfo = rest }
213 -> let request' = request { pathInfo = rest }
179 in runRouterEnv fmt router' (first, env) request' respond
214 in runRouterEnv fmt router' (first, env) request' respond
180− CaptureAllRouter router' ->
215+ CaptureAllRouter _ router' ->
181 let segments = pathInfo request
216 let segments = pathInfo request
182 request' = request { pathInfo = [] }
217 request' = request { pathInfo = [] }
183 in runRouterEnv fmt router' (segments, env) request' respond
218 in runRouterEnv fmt router' (segments, env) request' respond
servant-server/test/Servant/Server/RouterSpec.hs+127−6
@@ -9,7 +9,9 @@ import Control.Monad
9import Data.Proxy
9import Data.Proxy
10 (Proxy (..))
10 (Proxy (..))
11import Data.Text
11import Data.Text
12− (unpack)
12+ (Text, unpack)
13+import Data.Typeable
14+ (typeRep)
13import Network.HTTP.Types
15import Network.HTTP.Types
14 (Status (..))
16 (Status (..))
15import Network.Wai
17import Network.Wai
@@ -27,6 +29,7 @@ spec :: Spec
27spec = describe "Servant.Server.Internal.Router" $ do
29spec = describe "Servant.Server.Internal.Router" $ do
28 routerSpec
30 routerSpec
29 distributivitySpec
31 distributivitySpec
32+ serverLayoutSpec
30
33
31routerSpec :: Spec
34routerSpec :: Spec
32routerSpec = do
35routerSpec = do
@@ -51,7 +54,7 @@ routerSpec = do
51 toApp = toApplication . runRouter (const err404)
54 toApp = toApplication . runRouter (const err404)
52
55
53 cap :: Router ()
56 cap :: Router ()
54− cap = CaptureRouter $
57+ cap = CaptureRouter [hint] $
55 let delayed = addCapture (emptyDelayed $ Route pure) (const $ delayedFail err400)
58 let delayed = addCapture (emptyDelayed $ Route pure) (const $ delayedFail err400)
56 in leafRouter
59 in leafRouter
57 $ \env req res ->
60 $ \env req res ->
@@ -59,6 +62,9 @@ routerSpec = do
59 . const
62 . const
60 $ Route success
63 $ Route success
61
64
65+ hint :: CaptureHint
66+ hint = CaptureHint "anything" $ typeRep (Proxy :: Proxy ())
67+
62 router :: Router ()
68 router :: Router ()
63 router = leafRouter (\_ _ res -> res $ Route success)
69 router = leafRouter (\_ _ res -> res $ Route success)
64 `Choice` cap
70 `Choice` cap
@@ -98,12 +104,30 @@ distributivitySpec =
98 it "properly handles mixing static paths at different levels" $ do
104 it "properly handles mixing static paths at different levels" $ do
99 level `shouldHaveSameStructureAs` levelRef
105 level `shouldHaveSameStructureAs` levelRef
100
106
107+serverLayoutSpec :: Spec
108+serverLayoutSpec =
109+ describe "serverLayout" $ do
110+ it "correctly represents the example API" $ do
111+ exampleLayout `shouldHaveLayout` expectedExampleLayout
112+ it "aggregates capture hints when different" $ do
113+ captureDifferentTypes `shouldHaveLayout` expectedCaptureDifferentTypes
114+ it "nubs capture hints when equal" $ do
115+ captureSameType `shouldHaveLayout` expectedCaptureSameType
116+ it "properly displays CaptureAll hints" $ do
117+ captureAllLayout `shouldHaveLayout` expectedCaptureAllLayout
118+
101shouldHaveSameStructureAs ::
119shouldHaveSameStructureAs ::
102 (HasServer api1 '[], HasServer api2 '[]) => Proxy api1 -> Proxy api2 -> Expectation
120 (HasServer api1 '[], HasServer api2 '[]) => Proxy api1 -> Proxy api2 -> Expectation
103shouldHaveSameStructureAs p1 p2 =
121shouldHaveSameStructureAs p1 p2 =
104 unless (sameStructure (makeTrivialRouter p1) (makeTrivialRouter p2)) $
122 unless (sameStructure (makeTrivialRouter p1) (makeTrivialRouter p2)) $
105 expectationFailure ("expected:\n" ++ unpack (layout p2) ++ "\nbut got:\n" ++ unpack (layout p1))
123 expectationFailure ("expected:\n" ++ unpack (layout p2) ++ "\nbut got:\n" ++ unpack (layout p1))
106
124
125+shouldHaveLayout ::
126+ (HasServer api '[]) => Proxy api -> Text -> Expectation
127+shouldHaveLayout p l =
128+ unless (routerLayout (makeTrivialRouter p) == l) $
129+ expectationFailure ("expected:\n" ++ unpack l ++ "\nbut got:\n" ++ unpack (layout p))
130+
107makeTrivialRouter :: (HasServer layout '[]) => Proxy layout -> Router ()
131makeTrivialRouter :: (HasServer layout '[]) => Proxy layout -> Router ()
108makeTrivialRouter p =
132makeTrivialRouter p =
109 route p EmptyContext (emptyDelayed (FailFatal err501))
133 route p EmptyContext (emptyDelayed (FailFatal err501))
@@ -144,12 +168,12 @@ staticRef = Proxy
144-- structure:
168-- structure:
145
169
146type Dynamic =
170type Dynamic =
147− "a" :> Capture "foo" Int :> "b" :> End
171+ "a" :> Capture "foo" Int :> "b" :> End
148− :<|> "a" :> Capture "bar" Bool :> "c" :> End
172+ :<|> "a" :> Capture "foo" Int :> "c" :> End
149− :<|> "a" :> Capture "baz" Char :> "d" :> End
173+ :<|> "a" :> Capture "foo" Int :> "d" :> End
150
174
151type DynamicRef =
175type DynamicRef =
152− "a" :> Capture "anything" () :>
176+ "a" :> Capture "foo" Int :>
153 ("b" :> End :<|> "c" :> End :<|> "d" :> End)
177 ("b" :> End :<|> "c" :> End :<|> "d" :> End)
154
178
155dynamic :: Proxy Dynamic
179dynamic :: Proxy Dynamic
@@ -339,3 +363,100 @@ level = Proxy
339
363
340levelRef :: Proxy LevelRef
364levelRef :: Proxy LevelRef
341levelRef = Proxy
365levelRef = Proxy
366+
367+-- The example API for the 'layout' function.
368+-- Should get factorized by the 'choice' smart constructor.
369+type ExampleLayout =
370+ "a" :> "d" :> Get '[JSON] NoContent
371+ :<|> "b" :> Capture "x" Int :> Get '[JSON] Bool
372+ :<|> "c" :> Put '[JSON] Bool
373+ :<|> "a" :> "e" :> Get '[JSON] Int
374+ :<|> "b" :> Capture "x" Int :> Put '[JSON] Bool
375+ :<|> Raw
376+
377+exampleLayout :: Proxy ExampleLayout
378+exampleLayout = Proxy
379+
380+-- The expected representation of the example API layout
381+--
382+expectedExampleLayout :: Text
383+expectedExampleLayout =
384+ "/\n\
385+ \├─ a/\n\
386+ \│ ├─ d/\n\
387+ \│ │ └─•\n\
388+ \│ └─ e/\n\
389+ \│ └─•\n\
390+ \├─ b/\n\
391+ \│ └─ <x::Int>/\n\
392+ \│ ├─•\n\
393+ \│ ┆\n\
394+ \│ └─•\n\
395+ \├─ c/\n\
396+ \│ └─•\n\
397+ \┆\n\
398+ \└─ <raw>\n"
399+
400+-- A capture API with all capture types being the same
401+--
402+type CaptureSameType =
403+ "a" :> Capture "foo" Int :> "b" :> End
404+ :<|> "a" :> Capture "foo" Int :> "c" :> End
405+ :<|> "a" :> Capture "foo" Int :> "d" :> End
406+
407+captureSameType :: Proxy CaptureSameType
408+captureSameType = Proxy
409+
410+-- The expected representation of the CaptureSameType API layout.
411+--
412+expectedCaptureSameType :: Text
413+expectedCaptureSameType =
414+ "/\n\
415+ \└─ a/\n\
416+ \ └─ <foo::Int>/\n\
417+ \ ├─ b/\n\
418+ \ │ └─•\n\
419+ \ ├─ c/\n\
420+ \ │ └─•\n\
421+ \ └─ d/\n\
422+ \ └─•\n"
423+
424+-- A capture API capturing different types
425+--
426+type CaptureDifferentTypes =
427+ "a" :> Capture "foo" Int :> "b" :> End
428+ :<|> "a" :> Capture "bar" Bool :> "c" :> End
429+ :<|> "a" :> Capture "baz" Char :> "d" :> End
430+
431+captureDifferentTypes :: Proxy CaptureDifferentTypes
432+captureDifferentTypes = Proxy
433+
434+-- The expected representation of the CaptureDifferentTypes API layout.
435+--
436+expectedCaptureDifferentTypes :: Text
437+expectedCaptureDifferentTypes =
438+ "/\n\
439+ \└─ a/\n\
440+ \ └─ <foo::Int|bar::Bool|baz::Char>/\n\
441+ \ ├─ b/\n\
442+ \ │ └─•\n\
443+ \ ├─ c/\n\
444+ \ │ └─•\n\
445+ \ └─ d/\n\
446+ \ └─•\n"
447+
448+-- An API with a CaptureAll part
449+
450+type CaptureAllLayout = "a" :> CaptureAll "foos" Int :> End
451+
452+captureAllLayout :: Proxy CaptureAllLayout
453+captureAllLayout = Proxy
454+
455+-- The expected representation of the CaptureAllLayout API.
456+--
457+expectedCaptureAllLayout :: Text
458+expectedCaptureAllLayout =
459+ "/\n\
460+ \└─ a/\n\
461+ \ └─ <foos::[Int]>/\n\
462+ \ └─•\n"
servant-server/test/Servant/ServerSpec.hs+54−6
@@ -17,10 +17,14 @@ import Prelude.Compat
17
17
18import Control.Monad
18import Control.Monad
19 (forM_, unless, when)
19 (forM_, unless, when)
20+import Control.Monad.Reader (runReaderT, ask)
20import Control.Monad.Error.Class
21import Control.Monad.Error.Class
21 (MonadError (..))
22 (MonadError (..))
23+import Control.Monad.IO.Class (MonadIO(..))
22import Data.Aeson
24import Data.Aeson
23 (FromJSON, ToJSON, decode', encode)
25 (FromJSON, ToJSON, decode', encode)
26+import Data.Acquire
27+ (Acquire, mkAcquire)
24import qualified Data.ByteString as BS
28import qualified Data.ByteString as BS
25import qualified Data.ByteString.Base64 as Base64
29import qualified Data.ByteString.Base64 as Base64
26import Data.Char
30import Data.Char
@@ -52,19 +56,19 @@ import Servant.API
52 Delete, EmptyAPI, Fragment, Get, HasStatus (StatusOf), Header,
56 Delete, EmptyAPI, Fragment, Get, HasStatus (StatusOf), Header,
53 Headers, HttpVersion, IsSecure (..), JSON, Lenient,
57 Headers, HttpVersion, IsSecure (..), JSON, Lenient,
54 NoContent (..), NoContentVerb, NoFraming, OctetStream, Patch,
58 NoContent (..), NoContentVerb, NoFraming, OctetStream, Patch,
55− PlainText, Post, Put, QueryFlag, QueryParam, QueryParams, Raw,
59+ PlainText, Post, Put, QueryFlag, QueryParam, QueryParams, Raw, RawM,
56 RemoteHost, ReqBody, SourceIO, StdMethod (..), Stream, Strict,
60 RemoteHost, ReqBody, SourceIO, StdMethod (..), Stream, Strict,
57 UVerb, Union, Verb, WithStatus (..), addHeader)
61 UVerb, Union, Verb, WithStatus (..), addHeader)
58import Servant.Server
62import Servant.Server
59− (Context ((:.), EmptyContext), Handler, Server, Tagged (..),
63+ (Context ((:.), EmptyContext), Handler, Server, ServerT, Tagged (..),
60− emptyServer, err401, err403, err404, respond, serve,
64+ emptyServer, err401, err403, err404, hoistServer, respond, serve,
61 serveWithContext)
65 serveWithContext)
62import Servant.Test.ComprehensiveAPI
66import Servant.Test.ComprehensiveAPI
63import qualified Servant.Types.SourceT as S
67import qualified Servant.Types.SourceT as S
64import Test.Hspec
68import Test.Hspec
65 (Spec, context, describe, it, shouldBe, shouldContain)
69 (Spec, context, describe, it, shouldBe, shouldContain)
66import Test.Hspec.Wai
70import Test.Hspec.Wai
67− (get, liftIO, matchHeaders, matchStatus, shouldRespondWith,
71+ (get, matchHeaders, matchStatus, shouldRespondWith,
68 with, (<:>))
72 with, (<:>))
69import qualified Test.Hspec.Wai as THW
73import qualified Test.Hspec.Wai as THW
70
74
@@ -81,8 +85,11 @@ import Servant.Server.Internal.Context
81-- This declaration simply checks that all instances are in place.
85-- This declaration simply checks that all instances are in place.
82_ = serveWithContext comprehensiveAPI comprehensiveApiContext
86_ = serveWithContext comprehensiveAPI comprehensiveApiContext
83
87
84−comprehensiveApiContext :: Context '[NamedContext "foo" '[]]
88+comprehensiveApiContext :: Context '[NamedContext "foo" '[], Acquire Int]
89+comprehensiveApiContext =
85−comprehensiveApiContext = NamedContext EmptyContext :. EmptyContext
90+ NamedContext EmptyContext :.
91+ mkAcquire (pure 10) (\_ -> pure ()) :.
92+ EmptyContext
86
93
87-- * Specs
94-- * Specs
88
95
@@ -97,6 +104,7 @@ spec = do
97 reqBodySpec
104 reqBodySpec
98 headerSpec
105 headerSpec
99 rawSpec
106 rawSpec
107+ rawMSpec
100 alternativeSpec
108 alternativeSpec
101 responseHeadersSpec
109 responseHeadersSpec
102 uverbResponseHeadersSpec
110 uverbResponseHeadersSpec
@@ -605,6 +613,46 @@ rawSpec = do
605
613
606-- }}}
614-- }}}
607------------------------------------------------------------------------------
615------------------------------------------------------------------------------
616+-- * rawMSpec {{{
617+------------------------------------------------------------------------------
618+
619+type RawMApi = "foo" :> RawM
620+
621+rawMApi :: Proxy RawMApi
622+rawMApi = Proxy
623+
624+rawMServer :: (Monad m, MonadIO m, Show a) => (Request -> m a) -> ServerT RawMApi m
625+rawMServer f req resp = liftIO . resp . responseLBS ok200 [] . cs . show =<< f req
626+
627+rawMSpec :: Spec
628+rawMSpec = do
629+ describe "Servant.API.RawM" $ do
630+ it "gives access to monadic context" $ do
631+ flip runSession (serve rawMApi
632+ (hoistServer rawMApi (flip runReaderT (42 :: Integer)) (rawMServer (const ask)))) $ do
633+ response <- Network.Wai.Test.request defaultRequest{
634+ pathInfo = ["foo"]
635+ }
636+ liftIO $ do
637+ simpleBody response `shouldBe` "42"
638+
639+ it "lets users throw servant errors" $ do
640+ flip runSession (serve rawMApi (rawMServer (const $ throwError err404 >> pure (42 :: Integer)))) $ do
641+ response <- Network.Wai.Test.request defaultRequest{
642+ pathInfo = ["foo"]
643+ }
644+ liftIO $ do
645+ statusCode (simpleStatus response) `shouldBe` 404
646+
647+ it "gets the pathInfo modified" $ do
648+ flip runSession (serve rawMApi (rawMServer (pure . pathInfo))) $ do
649+ response <- Network.Wai.Test.request defaultRequest{
650+ pathInfo = ["foo", "bar"]
651+ }
652+ liftIO $ do
653+ simpleBody response `shouldBe` cs (show ["bar" :: String])
654+-- }}}
655+------------------------------------------------------------------------------
608-- * alternativeSpec {{{
656-- * alternativeSpec {{{
609------------------------------------------------------------------------------
657------------------------------------------------------------------------------
610type AlternativeApi =
658type AlternativeApi =
servant-swagger/example/example.cabal+2−2
@@ -1,15 +1,15 @@
1+cabal-version: 2.2
1name: example
2name: example
2version: 1.0
3version: 1.0
3synopsis: servant-swagger demonstration
4synopsis: servant-swagger demonstration
4description: servant-swagger demonstration
5description: servant-swagger demonstration
5−license: BSD3
6+license: BSD-3-Clause
6license-file: LICENSE
7license-file: LICENSE
7author: David Johnson, Nickolay Kudasov
8author: David Johnson, Nickolay Kudasov
8maintainer: nickolay.kudasov@gmail.com
9maintainer: nickolay.kudasov@gmail.com
9copyright: (c) 2015-2016, Servant contributors
10copyright: (c) 2015-2016, Servant contributors
10category: Web
11category: Web
11build-type: Simple
12build-type: Simple
12−cabal-version: >=1.10
13data-files:
13data-files:
14 swagger.json
14 swagger.json
15
15
servant-swagger/servant-swagger.cabal+16−15
@@ -1,11 +1,12 @@
1+cabal-version: 2.2
1name: servant-swagger
2name: servant-swagger
2version: 1.1.11
3version: 1.1.11
3synopsis: Generate a Swagger/OpenAPI/OAS 2.0 specification for your servant API.
4synopsis: Generate a Swagger/OpenAPI/OAS 2.0 specification for your servant API.
4description:
5description:
5− Swagger is a project used to describe and document RESTful APIs. The core of the
6+ Swagger is a project used to describe and document RESTful APIs. The core of the
6− project is the [OpenAPI Specification (OAS)](https://swagger.io/docs/specification/about/).
7+ project is the [OpenAPI Specification (OAS)](https://swagger.io/docs/specification/about/).
7− This library implements v2.0 of the spec. Unlike Servant it is language-agnostic and thus is
8+ This library implements v2.0 of the spec. Unlike Servant it is language-agnostic and thus is
8− quite popular among developers in different languages. It has also existed for a longer time
9+ quite popular among developers in different languages. It has also existed for a longer time
9 and has more helpful tooling.
10 and has more helpful tooling.
10 .
11 .
11 This package provides means to generate a Swagger/OAS specification for a Servant API
12 This package provides means to generate a Swagger/OAS specification for a Servant API
@@ -18,16 +19,15 @@ description:
18 * generating clients and servers in many languages using [Swagger Codegen](http://swagger.io/swagger-codegen/);
19 * generating clients and servers in many languages using [Swagger Codegen](http://swagger.io/swagger-codegen/);
19 .
20 .
20 * and [many others](http://swagger.io/open-source-integrations/).
21 * and [many others](http://swagger.io/open-source-integrations/).
21−homepage: https://github.com/haskell-servant/servant/servant-swagger
22+homepage: https://github.com/haskell-servant/servant/tree/master/servant-swagger#readme
22bug-reports: https://github.com/haskell-servant/servant/issues
23bug-reports: https://github.com/haskell-servant/servant/issues
23−license: BSD3
24+license: BSD-3-Clause
24license-file: LICENSE
25license-file: LICENSE
25author: Servant Contributors
26author: Servant Contributors
26maintainer: haskell-servant-maintainers@googlegroups.com
27maintainer: haskell-servant-maintainers@googlegroups.com
27copyright: (c) 2015-2018, Servant contributors
28copyright: (c) 2015-2018, Servant contributors
28category: Web, Servant, Swagger
29category: Web, Servant, Swagger
29build-type: Custom
30build-type: Custom
30−cabal-version: 1.18
31tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.2
31tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.2
32
32
33extra-source-files:
33extra-source-files:
@@ -50,6 +50,7 @@ source-repository head
50custom-setup
50custom-setup
51 setup-depends:
51 setup-depends:
52 base >=4.9 && <5,
52 base >=4.9 && <5,
53+ Cabal >= 1.24,
53 cabal-doctest >=1.0.6 && <1.1
54 cabal-doctest >=1.0.6 && <1.1
54
55
55library
56library
@@ -71,15 +72,15 @@ library
71 build-depends: aeson >=1.4.2.0 && <3
72 build-depends: aeson >=1.4.2.0 && <3
72 , aeson-pretty >=0.8.7 && <0.9
73 , aeson-pretty >=0.8.7 && <0.9
73 , base >=4.9.1.0 && <5
74 , base >=4.9.1.0 && <5
74− , base-compat >=0.10.5 && <0.12
75+ , base-compat >=0.10.5 && <0.13
75− , bytestring >=0.10.8.1 && <0.11
76+ , bytestring >=0.10.8.1 && <0.12
76 , http-media >=0.7.1.3 && <0.9
77 , http-media >=0.7.1.3 && <0.9
77 , insert-ordered-containers >=0.2.1.0 && <0.3
78 , insert-ordered-containers >=0.2.1.0 && <0.3
78 , lens >=4.17 && <6
79 , lens >=4.17 && <6
79− , servant >=0.18.1 && <0.19
80+ , servant >=0.18.2 && <0.20
80 , singleton-bool >=0.1.4 && <0.2
81 , singleton-bool >=0.1.4 && <0.2
81 , swagger2 >=2.3.0.1 && <3
82 , swagger2 >=2.3.0.1 && <3
82− , text >=1.2.3.0 && <1.3
83+ , text >=1.2.3.0 && <2.1
83 , unordered-containers >=0.2.9.0 && <0.3
84 , unordered-containers >=0.2.9.0 && <0.3
84
85
85 , hspec
86 , hspec
@@ -91,7 +92,7 @@ test-suite doctests
91 build-depends:
92 build-depends:
92 base,
93 base,
93 directory >= 1.0,
94 directory >= 1.0,
94− doctest >= 0.18 && <0.19,
95+ doctest >= 0.17 && <0.21,
95 servant,
96 servant,
96 QuickCheck,
97 QuickCheck,
97 filepath
98 filepath
@@ -105,14 +106,14 @@ test-suite spec
105 type: exitcode-stdio-1.0
106 type: exitcode-stdio-1.0
106 hs-source-dirs: test
107 hs-source-dirs: test
107 main-is: Spec.hs
108 main-is: Spec.hs
108− build-tool-depends: hspec-discover:hspec-discover >=2.6.0 && <2.8
109+ build-tool-depends: hspec-discover:hspec-discover >=2.6.0 && <2.11
109 build-depends: base
110 build-depends: base
110 , base-compat
111 , base-compat
111 , aeson >=1.4.2.0 && <3
112 , aeson >=1.4.2.0 && <3
112− , hspec >=2.6.0 && <2.8
113+ , hspec >=2.6.0 && <2.11
113 , QuickCheck
114 , QuickCheck
114 , lens
115 , lens
115− , lens-aeson >=1.0.2 && <1.2
116+ , lens-aeson >=1.0.2 && <1.3
116 , servant
117 , servant
117 , servant-swagger
118 , servant-swagger
118 , swagger2
119 , swagger2
servant-swagger/src/Servant/Swagger.hs+5−3
@@ -55,6 +55,7 @@ import Servant.Swagger.Internal.Orphans ()
55-- >>> import Data.Typeable
55-- >>> import Data.Typeable
56-- >>> import GHC.Generics
56-- >>> import GHC.Generics
57-- >>> import Servant.API
57-- >>> import Servant.API
58+-- >>> import System.Environment
58-- >>> import Test.Hspec
59-- >>> import Test.Hspec
59-- >>> import Test.QuickCheck
60-- >>> import Test.QuickCheck
60-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL8
61-- >>> import qualified Data.ByteString.Lazy.Char8 as BSL8
@@ -64,6 +65,7 @@ import Servant.Swagger.Internal.Orphans ()
64-- >>> :set -XGeneralizedNewtypeDeriving
65-- >>> :set -XGeneralizedNewtypeDeriving
65-- >>> :set -XOverloadedStrings
66-- >>> :set -XOverloadedStrings
66-- >>> :set -XTypeOperators
67-- >>> :set -XTypeOperators
68+-- >>> setEnv "HSPEC_COLOR" "no"
67-- >>> data User = User { name :: String, age :: Int } deriving (Show, Generic, Typeable)
69-- >>> data User = User { name :: String, age :: Int } deriving (Show, Generic, Typeable)
68-- >>> newtype UserId = UserId Integer deriving (Show, Generic, Typeable, ToJSON)
70-- >>> newtype UserId = UserId Integer deriving (Show, Generic, Typeable, ToJSON)
69-- >>> instance ToJSON User
71-- >>> instance ToJSON User
@@ -160,11 +162,11 @@ import Servant.Swagger.Internal.Orphans ()
160-- >>> instance Arbitrary UserId where arbitrary = UserId <$> arbitrary
162-- >>> instance Arbitrary UserId where arbitrary = UserId <$> arbitrary
161-- >>> hspec $ validateEveryToJSON (Proxy :: Proxy UserAPI)
163-- >>> hspec $ validateEveryToJSON (Proxy :: Proxy UserAPI)
162-- <BLANKLINE>
164-- <BLANKLINE>
163−-- [User]
165+-- [User]...
164-- ...
166-- ...
165−-- User
167+-- User...
166-- ...
168-- ...
167−-- UserId
169+-- UserId...
168-- ...
170-- ...
169-- Finished in ... seconds
171-- Finished in ... seconds
170-- 3 examples, 0 failures
172-- 3 examples, 0 failures
servant-swagger/src/Servant/Swagger/Internal.hs+15
@@ -3,11 +3,13 @@
3{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE OverloadedLists #-}
6{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE PolyKinds #-}
8{-# LANGUAGE PolyKinds #-}
8{-# LANGUAGE RankNTypes #-}
9{-# LANGUAGE RankNTypes #-}
9{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE TypeOperators #-}
11{-# LANGUAGE TypeOperators #-}
12+{-# LANGUAGE TypeFamilies #-}
11#if __GLASGOW_HASKELL__ >= 806
13#if __GLASGOW_HASKELL__ >= 806
12{-# LANGUAGE UndecidableInstances #-}
14{-# LANGUAGE UndecidableInstances #-}
13#endif
15#endif
@@ -30,11 +32,13 @@ import qualified Data.Swagger as Swagger
30import Data.Swagger.Declare
32import Data.Swagger.Declare
31import Data.Text (Text)
33import Data.Text (Text)
32import qualified Data.Text as Text
34import qualified Data.Text as Text
35+import GHC.Generics (D1, Meta(..), Rep)
33import GHC.TypeLits
36import GHC.TypeLits
34import Network.HTTP.Media (MediaType)
37import Network.HTTP.Media (MediaType)
35import Servant.API
38import Servant.API
36import Servant.API.Description (FoldDescription,
39import Servant.API.Description (FoldDescription,
37 reflectDescription)
40 reflectDescription)
41+import Servant.API.Generic (ToServantApi, AsApi)
38import Servant.API.Modifiers (FoldRequired)
42import Servant.API.Modifiers (FoldRequired)
39
43
40import Servant.Swagger.Internal.TypeLevel.API
44import Servant.Swagger.Internal.TypeLevel.API
@@ -149,6 +153,10 @@ mkEndpointNoContentVerb path _ = mempty
149addParam :: Param -> Swagger -> Swagger
153addParam :: Param -> Swagger -> Swagger
150addParam param = allOperations.parameters %~ (Inline param :)
154addParam param = allOperations.parameters %~ (Inline param :)
151
155
156+-- | Add a tag to every operation in the spec.
157+addTag :: Text -> Swagger -> Swagger
158+addTag tag = allOperations.tags %~ ([tag] <>)
159+
152-- | Add accepted content types to every operation in the spec.
160-- | Add accepted content types to every operation in the spec.
153addConsumes :: [MediaType] -> Swagger -> Swagger
161addConsumes :: [MediaType] -> Swagger -> Swagger
154addConsumes cs = allOperations.consumes %~ (<> Just (MimeList cs))
162addConsumes cs = allOperations.consumes %~ (<> Just (MimeList cs))
@@ -296,6 +304,10 @@ instance (HasSwagger sub) => HasSwagger (HttpVersion :> sub) where
296instance (HasSwagger sub) => HasSwagger (WithNamedContext x c sub) where
304instance (HasSwagger sub) => HasSwagger (WithNamedContext x c sub) where
297 toSwagger _ = toSwagger (Proxy :: Proxy sub)
305 toSwagger _ = toSwagger (Proxy :: Proxy sub)
298
306
307+-- | @'WithResource'@ combinator does not change our specification at all.
308+instance (HasSwagger sub) => HasSwagger (WithResource res :> sub) where
309+ toSwagger _ = toSwagger (Proxy :: Proxy sub)
310+
299instance (KnownSymbol sym, HasSwagger sub) => HasSwagger (sym :> sub) where
311instance (KnownSymbol sym, HasSwagger sub) => HasSwagger (sym :> sub) where
300 toSwagger _ = prependPath piece (toSwagger (Proxy :: Proxy sub))
312 toSwagger _ = prependPath piece (toSwagger (Proxy :: Proxy sub))
301 where
313 where
@@ -439,6 +451,9 @@ instance (ToSchema a, Accept ct, HasSwagger sub, KnownSymbol (FoldDescription mo
439 & required ?~ True
451 & required ?~ True
440 & schema .~ ParamBody ref
452 & schema .~ ParamBody ref
441
453
454+instance (HasSwagger (ToServantApi routes), KnownSymbol datatypeName, Rep (routes AsApi) ~ D1 ('MetaData datatypeName moduleName packageName isNewtype) f) => HasSwagger (NamedRoutes routes) where
455+ toSwagger _ = addTag (Text.pack $ symbolVal (Proxy :: Proxy datatypeName)) (toSwagger (Proxy :: Proxy (ToServantApi routes)))
456+
442-- =======================================================================
457-- =======================================================================
443-- Below are the definitions that should be in Servant.API.ContentTypes
458-- Below are the definitions that should be in Servant.API.ContentTypes
444-- =======================================================================
459-- =======================================================================
servant-swagger/src/Servant/Swagger/Internal/Test.hs+8−6
@@ -29,10 +29,12 @@ import Servant.Swagger.Internal.TypeLevel
29-- >>> import Control.Applicative
29-- >>> import Control.Applicative
30-- >>> import GHC.Generics
30-- >>> import GHC.Generics
31-- >>> import Test.QuickCheck
31-- >>> import Test.QuickCheck
32+-- >>> import System.Environment (setEnv)
32-- >>> :set -XDeriveGeneric
33-- >>> :set -XDeriveGeneric
33-- >>> :set -XGeneralizedNewtypeDeriving
34-- >>> :set -XGeneralizedNewtypeDeriving
34-- >>> :set -XDataKinds
35-- >>> :set -XDataKinds
35-- >>> :set -XTypeOperators
36-- >>> :set -XTypeOperators
37+-- >>> setEnv "HSPEC_COLOR" "no"
36
38
37-- | Verify that every type used with @'JSON'@ content type in a servant API
39-- | Verify that every type used with @'JSON'@ content type in a servant API
38-- has compatible @'ToJSON'@ and @'ToSchema'@ instances using @'validateToJSON'@.
40-- has compatible @'ToJSON'@ and @'ToSchema'@ instances using @'validateToJSON'@.
@@ -53,10 +55,10 @@ import Servant.Swagger.Internal.TypeLevel
53--
55--
54-- >>> hspec $ context "ToJSON matches ToSchema" $ validateEveryToJSON (Proxy :: Proxy UserAPI)
56-- >>> hspec $ context "ToJSON matches ToSchema" $ validateEveryToJSON (Proxy :: Proxy UserAPI)
55-- <BLANKLINE>
57-- <BLANKLINE>
56−-- ToJSON matches ToSchema
58+-- ToJSON matches ToSchema...
57−-- User
59+-- User...
58-- ...
60-- ...
59−-- UserId
61+-- UserId...
60-- ...
62-- ...
61-- Finished in ... seconds
63-- Finished in ... seconds
62-- 2 examples, 0 failures
64-- 2 examples, 0 failures
@@ -118,11 +120,11 @@ validateEveryToJSONWithPatternChecker checker _ = props
118-- :}
120-- :}
119-- <BLANKLINE>
121-- <BLANKLINE>
120-- read . show == id
122-- read . show == id
121−-- Bool
123+-- Bool...
122-- ...
124-- ...
123−-- Int
125+-- Int...
124-- ...
126-- ...
125−-- [Char]
127+-- [Char]...
126-- ...
128-- ...
127-- Finished in ... seconds
129-- Finished in ... seconds
128-- 3 examples, 0 failures
130-- 3 examples, 0 failures
servant/CHANGELOG.md+121
@@ -1,5 +1,126 @@
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
2
2
3+Package versions follow the [Package Versioning Policy](https://pvp.haskell.org/): in A.B.C, bumps to either A or B represent major versions.
4+
5+0.19.1
6+------
7+
8+Compatibility with GHC 9.4, see [PR #1592](https://github.com/haskell-servant/servant/pull/1592).
9+
10+0.19
11+----
12+
13+### Significant changes
14+
15+- Drop support for GHC < 8.6.
16+- Support GHC 9.0 (GHC 9.2 should work as well, but isn't fully tested yet).
17+- Support Aeson 2 ([#1475](https://github.com/haskell-servant/servant/pull/1475)),
18+ which fixes a [DOS vulnerability](https://github.com/haskell/aeson/issues/864)
19+ related to hash collisions.
20+- Add `NamedRoutes` combinator, making support for records first-class in Servant
21+ ([#1388](https://github.com/haskell-servant/servant/pull/1388)).
22+
23+ Users can now directly mark part as an API as defined by a record, instead of
24+ using `(:<|>)` to combine routes. Concretely, the anonymous:
25+
26+ ```haskell
27+ type API =
28+ "version" :> Get '[JSON] String :<|>
29+ "products" :> Get '[JSON] [Product]
30+ ```
31+
32+ can be replaced with the explicitly-named:
33+
34+ ```haskell
35+ type API = NamedRoutes NamedAPI
36+ data NamedAPI mode = NamedAPI
37+ { version :: mode :- "version" :> Get '[JSON] String
38+ , products :: mode :- "products" :> Get '[JSON] [Product]
39+ }
40+ ```
41+
42+ `NamedRoutes` builds upon `servant-generic`, but improves usability by freeing
43+ users from the need to perform `toServant` / `fromServant` conversions
44+ manually. Serving `NamedRoutes NamedAPI` is now done directly by providing a
45+ record of handlers, and servant generates clients directly as records as well.
46+ In particular, it makes it much more practical to work with nested hierarchies
47+ of named routes.
48+
49+ Two convenience functions, `(//)` and `(/:)`, have been added to make the
50+ usage of named route hierarchies more pleasant:
51+
52+ ```haskell
53+ rootClient :: RootApi (AsClientT ClientM)
54+ rootClient = client (Proxy @API)
55+
56+ helloClient :: String -> ClientM String
57+ helloClient name = rootClient // hello /: name
58+
59+ endpointClient :: ClientM Person
60+ endpointClient = rootClient // subApi /: "foobar123" // endpoint
61+
62+ type Api = NamedRoutes RootApi
63+
64+ data RootApi mode = RootApi
65+ { subApi :: mode :- Capture "token" String :> NamedRoutes SubApi
66+ , hello :: mode :- Capture "name" String :> Get '[JSON] String
67+ , …
68+ } deriving Generic
69+
70+ data SubApi mode = SubApi
71+ { endpoint :: mode :- Get '[JSON] Person
72+ , …
73+ } deriving Generic
74+ ```
75+
76+- Add custom type errors for partially applied combinators
77+ ([#1289](https://github.com/haskell-servant/servant/pull/1289),
78+ [#1486](https://github.com/haskell-servant/servant/pull/1486)).
79+
80+ For example, forgetting to document the expected type for a query parameter,
81+ as in:
82+
83+ ``` haskell
84+ type API = QueryParam "param" :> Get '[JSON] NoContent
85+ ```
86+
87+ will raise to the following error when trying to serve the API:
88+
89+ ```
90+ • There is no instance for HasServer (QueryParam'
91+ '[Optional, Strict] "param" :> ...)
92+ QueryParam' '[Optional, Strict] "1" expects 1 more arguments
93+ ```
94+
95+ As a consequence of this change, unsaturated types are now forbidden before `(:>)`.
96+
97+- Add a `HeadNoContent` verb ([#1502](https://github.com/haskell-servant/servant/pull/1502)).
98+
99+- *servant-client* / *servant-client-core* / *servant-http-streams*:
100+ Fix erroneous behavior, where only 2XX status codes would be considered
101+ successful, irrelevant of the status parameter specified by the verb
102+ combinator. ([#1469](https://github.com/haskell-servant/servant/pull/1469))
103+
104+- *servant-client* / *servant-client-core*: Fix `Show` instance for
105+ `Servant.Client.Core.Request`.
106+
107+
108+- *servant-client* / *servant-client-core*: Allow passing arbitrary binary data
109+ in Query parameters.
110+ ([#1432](https://github.com/haskell-servant/servant/pull/1432)).
111+
112+- *servant-docs*: Generate sample cURL requests
113+ ([#1401](https://github.com/haskell-servant/servant/pull/1401/files)).
114+
115+ Breaking change: requires sample header values to be supplied with `headers`.
116+
117+### Other changes
118+
119+- Various bit rotten cookbooks have been updated and re-introduced on
120+ [docs.servant.dev](https://docs.servant.dev).
121+
122+- Various version bumps.
123+
30.18.3
1240.18.3
4------
125------
5
126
servant/servant.cabal+12−11
@@ -1,6 +1,6 @@
1−cabal-version: >=1.10
1+cabal-version: 2.2
2name: servant
2name: servant
3−version: 0.18.3
3+version: 0.19.1
4
4
5synopsis: A family of combinators for defining webservices APIs
5synopsis: A family of combinators for defining webservices APIs
6category: Servant, Web
6category: Servant, Web
@@ -62,6 +62,7 @@ library
62 Servant.API.Vault
62 Servant.API.Vault
63 Servant.API.Verbs
63 Servant.API.Verbs
64 Servant.API.WithNamedContext
64 Servant.API.WithNamedContext
65+ Servant.API.WithResource
65
66
66 -- Types
67 -- Types
67 exposed-modules:
68 exposed-modules:
@@ -80,25 +81,25 @@ library
80 --
81 --
81 -- note: mtl lower bound is so low because of GHC-7.8
82 -- note: mtl lower bound is so low because of GHC-7.8
82 build-depends:
83 build-depends:
83− base >= 4.9 && < 4.16
84+ base >= 4.9 && < 4.18
84 , bytestring >= 0.10.8.1 && < 0.12
85 , bytestring >= 0.10.8.1 && < 0.12
85 , constraints >= 0.2
86 , constraints >= 0.2
86− , mtl >= 2.2.2 && < 2.3
87+ , mtl ^>= 2.2.2 || ^>= 2.3.1
87 , sop-core >= 0.4.0.0 && < 0.6
88 , sop-core >= 0.4.0.0 && < 0.6
88− , transformers >= 0.5.2.0 && < 0.6
89+ , transformers >= 0.5.2.0 && < 0.7
89− , text >= 1.2.3.0 && < 1.3
90+ , text >= 1.2.3.0 && < 2.1
90
91
91
92
92 -- We depend (heavily) on the API of these packages:
93 -- We depend (heavily) on the API of these packages:
93 -- i.e. re-export, or allow using without direct dependency
94 -- i.e. re-export, or allow using without direct dependency
94 build-depends:
95 build-depends:
95− http-api-data >= 0.4.1 && < 0.4.4
96+ http-api-data >= 0.4.1 && < 0.5.1
96 , singleton-bool >= 0.1.4 && < 0.1.7
97 , singleton-bool >= 0.1.4 && < 0.1.7
97
98
98 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
99 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
99 -- Here can be exceptions if we really need features from the newer versions.
100 -- Here can be exceptions if we really need features from the newer versions.
100 build-depends:
101 build-depends:
101− base-compat >= 0.10.5 && < 0.12
102+ base-compat >= 0.10.5 && < 0.13
102 , aeson >= 1.4.1.0 && < 3
103 , aeson >= 1.4.1.0 && < 3
103 , attoparsec >= 0.13.2.2 && < 0.15
104 , attoparsec >= 0.13.2.2 && < 0.15
104 , bifunctors >= 5.5.3 && < 5.6
105 , bifunctors >= 5.5.3 && < 5.6
@@ -106,7 +107,7 @@ library
106 , deepseq >= 1.4.2.0 && < 1.5
107 , deepseq >= 1.4.2.0 && < 1.5
107 , http-media >= 0.7.1.3 && < 0.9
108 , http-media >= 0.7.1.3 && < 0.9
108 , http-types >= 0.12.2 && < 0.13
109 , http-types >= 0.12.2 && < 0.13
109− , mmorph >= 1.1.2 && < 1.2
110+ , mmorph >= 1.1.2 && < 1.3
110 , network-uri >= 2.6.1.0 && < 2.7
111 , network-uri >= 2.6.1.0 && < 2.7
111 , QuickCheck >= 2.12.6.1 && < 2.15
112 , QuickCheck >= 2.12.6.1 && < 2.15
112 , string-conversions >= 0.4.0.1 && < 0.5
113 , string-conversions >= 0.4.0.1 && < 0.5
@@ -166,9 +167,9 @@ test-suite spec
166
167
167 -- Additional dependencies
168 -- Additional dependencies
168 build-depends:
169 build-depends:
169− hspec >= 2.6.0 && < 2.9
170+ hspec >= 2.6.0 && < 2.10
170 , QuickCheck >= 2.12.6.1 && < 2.15
171 , QuickCheck >= 2.12.6.1 && < 2.15
171 , quickcheck-instances >= 0.3.19 && < 0.4
172 , quickcheck-instances >= 0.3.19 && < 0.4
172
173
173 build-tool-depends:
174 build-tool-depends:
174− hspec-discover:hspec-discover >= 2.6.0 && < 2.9
175+ hspec-discover:hspec-discover >= 2.6.0 && < 2.10
servant/src/Servant/API.hs+11−3
@@ -31,6 +31,8 @@ module Servant.API (
31 -- | Access the location for arbitrary data to be shared by applications and middleware
31 -- | Access the location for arbitrary data to be shared by applications and middleware
32 module Servant.API.WithNamedContext,
32 module Servant.API.WithNamedContext,
33 -- | Access context entries in combinators in servant-server
33 -- | Access context entries in combinators in servant-server
34+ module Servant.API.WithResource,
35+ -- | Access a managed resource scoped to a single request
34
36
35 -- * Actual endpoints, distinguished by HTTP method
37 -- * Actual endpoints, distinguished by HTTP method
36 module Servant.API.Verbs,
38 module Servant.API.Verbs,
@@ -38,6 +40,7 @@ module Servant.API (
38
40
39 -- * Sub-APIs defined as records of routes
41 -- * Sub-APIs defined as records of routes
40 module Servant.API.NamedRoutes,
42 module Servant.API.NamedRoutes,
43+ module Servant.API.Generic,
41
44
42 -- * Streaming endpoints, distinguished by HTTP method
45 -- * Streaming endpoints, distinguished by HTTP method
43 module Servant.API.Stream,
46 module Servant.API.Stream,
@@ -100,6 +103,9 @@ import Servant.API.Experimental.Auth
100 (AuthProtect)
103 (AuthProtect)
101import Servant.API.Fragment
104import Servant.API.Fragment
102 (Fragment)
105 (Fragment)
106+import Servant.API.Generic
107+ (AsApi, GServantProduct, GenericMode ((:-)), GenericServant,
108+ ToServant, ToServantApi, fromServant, genericApi, toServant)
103import Servant.API.Header
109import Servant.API.Header
104 (Header, Header')
110 (Header, Header')
105import Servant.API.HttpVersion
111import Servant.API.HttpVersion
@@ -108,10 +114,12 @@ import Servant.API.IsSecure
108 (IsSecure (..))
114 (IsSecure (..))
109import Servant.API.Modifiers
115import Servant.API.Modifiers
110 (Lenient, Optional, Required, Strict)
116 (Lenient, Optional, Required, Strict)
117+import Servant.API.NamedRoutes
118+ (NamedRoutes)
111import Servant.API.QueryParam
119import Servant.API.QueryParam
112 (QueryFlag, QueryParam, QueryParam', QueryParams)
120 (QueryFlag, QueryParam, QueryParam', QueryParams)
113import Servant.API.Raw
121import Servant.API.Raw
114− (Raw)
122+ (Raw, RawM)
115import Servant.API.RemoteHost
123import Servant.API.RemoteHost
116 (RemoteHost)
124 (RemoteHost)
117import Servant.API.ReqBody
125import Servant.API.ReqBody
@@ -133,8 +141,6 @@ import Servant.API.UVerb
133 Unique, WithStatus (..), inject, statusOf)
141 Unique, WithStatus (..), inject, statusOf)
134import Servant.API.Vault
142import Servant.API.Vault
135 (Vault)
143 (Vault)
136−import Servant.API.NamedRoutes
137− (NamedRoutes)
138import Servant.API.Verbs
144import Servant.API.Verbs
139 (Delete, DeleteAccepted, DeleteNoContent,
145 (Delete, DeleteAccepted, DeleteNoContent,
140 DeleteNonAuthoritative, Get, GetAccepted, GetNoContent,
146 DeleteNonAuthoritative, Get, GetAccepted, GetNoContent,
@@ -146,6 +152,8 @@ import Servant.API.Verbs
146 ReflectMethod (reflectMethod), StdMethod (..), Verb)
152 ReflectMethod (reflectMethod), StdMethod (..), Verb)
147import Servant.API.WithNamedContext
153import Servant.API.WithNamedContext
148 (WithNamedContext)
154 (WithNamedContext)
155+import Servant.API.WithResource
156+ (WithResource)
149import Servant.Links
157import Servant.Links
150 (HasLink (..), IsElem, IsElem', Link, URI (..), safeLink)
158 (HasLink (..), IsElem, IsElem', Link, URI (..), safeLink)
151import Web.HttpApiData
159import Web.HttpApiData
servant/src/Servant/API/ContentTypes.hs+1−1
@@ -295,7 +295,7 @@ instance {-# OVERLAPPABLE #-}
295-- then this would be taken care of. However there is no more specific instance
295-- then this would be taken care of. However there is no more specific instance
296-- between that and 'MimeRender JSON a', so we do this instead
296-- between that and 'MimeRender JSON a', so we do this instead
297instance {-# OVERLAPPING #-} ( Accept ctyp ) => AllMimeRender '[ctyp] NoContent where
297instance {-# OVERLAPPING #-} ( Accept ctyp ) => AllMimeRender '[ctyp] NoContent where
298− allMimeRender _ _ = map (, "") $ NE.toList $ contentTypes pctyp
298+ allMimeRender _ NoContent = map (, "") $ NE.toList $ contentTypes pctyp
299 where
299 where
300 pctyp = Proxy :: Proxy ctyp
300 pctyp = Proxy :: Proxy ctyp
301
301
servant/src/Servant/API/Raw.hs+3
@@ -15,3 +15,6 @@ import Data.Typeable
15-- <https://hackage.haskell.org/package/servant-server/docs/Servant-Server-StaticFiles.html Servant.Server.StaticFiles>
15-- <https://hackage.haskell.org/package/servant-server/docs/Servant-Server-StaticFiles.html Servant.Server.StaticFiles>
16-- to serve static files stored in a particular directory on your filesystem
16-- to serve static files stored in a particular directory on your filesystem
17data Raw deriving Typeable
17data Raw deriving Typeable
18+
19+-- | Variant of 'Raw' that lets you access the underlying monadic context to process the request.
20+data RawM deriving Typeable
servant/src/Servant/API/ResponseHeaders.hs+29−11
@@ -3,13 +3,12 @@
3{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE DeriveFunctor #-}
4{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE FunctionalDependencies #-}
7{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
8−{-# LANGUAGE KindSignatures #-}
7+{-# LANGUAGE FunctionalDependencies #-}
9−{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE PolyKinds #-}
8{-# LANGUAGE PolyKinds #-}
11{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE TypeFamilies #-}
10{-# LANGUAGE TypeFamilies #-}
11+{-# LANGUAGE TypeFamilyDependencies #-}
13{-# LANGUAGE TypeOperators #-}
12{-# LANGUAGE TypeOperators #-}
14{-# LANGUAGE UndecidableInstances #-}
13{-# LANGUAGE UndecidableInstances #-}
15{-# OPTIONS_HADDOCK not-home #-}
14{-# OPTIONS_HADDOCK not-home #-}
@@ -51,11 +50,11 @@ import Web.HttpApiData
51
50
52import Prelude ()
51import Prelude ()
53import Prelude.Compat
52import Prelude.Compat
54−import Servant.API.ContentTypes
55− (JSON, PlainText, FormUrlEncoded, OctetStream,
56− MimeRender(..))
57import Servant.API.Header
53import Servant.API.Header
58 (Header)
54 (Header)
55+import Servant.API.UVerb.Union
56+import qualified Data.SOP.BasicFunctors as SOP
57+import qualified Data.SOP.NS as SOP
59
58
60-- | Response Header objects. You should never need to construct one directly.
59-- | Response Header objects. You should never need to construct one directly.
61-- Instead, use 'addOptionalHeader'.
60-- Instead, use 'addOptionalHeader'.
@@ -117,7 +116,7 @@ instance {-# OVERLAPPABLE #-} ( FromHttpApiData v, BuildHeadersTo xs, KnownSymbo
117 `HCons` buildHeadersTo headers
116 `HCons` buildHeadersTo headers
118 Right h -> Header h `HCons` buildHeadersTo headers
117 Right h -> Header h `HCons` buildHeadersTo headers
119
118
120−-- * Getting
119+-- * Getting headers
121
120
122class GetHeaders ls where
121class GetHeaders ls where
123 getHeaders :: ls -> [HTTP.Header]
122 getHeaders :: ls -> [HTTP.Header]
@@ -158,23 +157,42 @@ instance (KnownSymbol h, GetHeadersFromHList rest, ToHttpApiData v)
158 where
157 where
159 getHeaders' hs = getHeadersFromHList $ getHeadersHList hs
158 getHeaders' hs = getHeadersFromHList $ getHeadersHList hs
160
159
161−-- * Adding
160+-- * Adding headers
162
161
163-- We need all these fundeps to save type inference
162-- We need all these fundeps to save type inference
164class AddHeader h v orig new
163class AddHeader h v orig new
165 | h v orig -> new, new -> h, new -> v, new -> orig where
164 | h v orig -> new, new -> h, new -> v, new -> orig where
166 addOptionalHeader :: ResponseHeader h v -> orig -> new -- ^ N.B.: The same header can't be added multiple times
165 addOptionalHeader :: ResponseHeader h v -> orig -> new -- ^ N.B.: The same header can't be added multiple times
167
166
168−
167+-- In this instance, we add a Header on top of something that is already decorated with some headers
169instance {-# OVERLAPPING #-} ( KnownSymbol h, ToHttpApiData v )
168instance {-# OVERLAPPING #-} ( KnownSymbol h, ToHttpApiData v )
170 => AddHeader h v (Headers (fst ': rest) a) (Headers (Header h v ': fst ': rest) a) where
169 => AddHeader h v (Headers (fst ': rest) a) (Headers (Header h v ': fst ': rest) a) where
171 addOptionalHeader hdr (Headers resp heads) = Headers resp (HCons hdr heads)
170 addOptionalHeader hdr (Headers resp heads) = Headers resp (HCons hdr heads)
172
171
172+-- In this instance, 'a' parameter is decorated with a Header.
173−instance {-# OVERLAPPABLE #-} ( KnownSymbol h, ToHttpApiData v
173+instance {-# OVERLAPPABLE #-} ( KnownSymbol h, ToHttpApiData v , new ~ Headers '[Header h v] a)
174− , new ~ (Headers '[Header h v] a) )
175 => AddHeader h v a new where
174 => AddHeader h v a new where
176 addOptionalHeader hdr resp = Headers resp (HCons hdr HNil)
175 addOptionalHeader hdr resp = Headers resp (HCons hdr HNil)
177
176
177+-- Instances to decorate all responses in a 'Union' with headers. The functional
178+-- dependencies force us to consider singleton lists as the base case in the
179+-- recursion (it is impossible to determine h and v otherwise from old / new
180+-- responses if the list is empty).
181+instance (AddHeader h v old new) => AddHeader h v (Union '[old]) (Union '[new]) where
182+ addOptionalHeader hdr resp =
183+ SOP.Z $ SOP.I $ addOptionalHeader hdr $ SOP.unI $ SOP.unZ $ resp
184+
185+instance
186+ ( AddHeader h v old new, AddHeader h v (Union oldrest) (Union newrest)
187+ -- This ensures that the remainder of the response list is _not_ empty
188+ -- It is necessary to prevent the two instances for union types from
189+ -- overlapping.
190+ , oldrest ~ (a ': as), newrest ~ (b ': bs))
191+ => AddHeader h v (Union (old ': (a ': as))) (Union (new ': (b ': bs))) where
192+ addOptionalHeader hdr resp = case resp of
193+ SOP.Z (SOP.I rHead) -> SOP.Z $ SOP.I $ addOptionalHeader hdr rHead
194+ SOP.S rOthers -> SOP.S $ addOptionalHeader hdr rOthers
195+
178-- | @addHeader@ adds a header to a response. Note that it changes the type of
196-- | @addHeader@ adds a header to a response. Note that it changes the type of
179-- the value in the following ways:
197-- the value in the following ways:
180--
198--
servant/src/Servant/API/Status.hs+6−1
@@ -1,10 +1,15 @@
1{-# LANGUAGE DataKinds #-}
1{-# LANGUAGE DataKinds #-}
2-- Flexible instances is necessary on GHC 8.4 and earlier
2-- Flexible instances is necessary on GHC 8.4 and earlier
3{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
4module Servant.API.Status where
5module Servant.API.Status where
5
6
7+import GHC.TypeLits (KnownNat, natVal)
6import Network.HTTP.Types.Status
8import Network.HTTP.Types.Status
7−import GHC.TypeLits
9+
10+-- | Retrieve a known or unknown Status from a KnownNat
11+statusFromNat :: forall a proxy. KnownNat a => proxy a -> Status
12+statusFromNat = toEnum . fromInteger . natVal
8
13
9-- | Witness that a type-level natural number corresponds to a HTTP status code
14-- | Witness that a type-level natural number corresponds to a HTTP status code
10class KnownNat n => KnownStatus n where
15class KnownNat n => KnownStatus n where
servant/src/Servant/API/TypeLevel.hs+3−3
@@ -110,7 +110,7 @@ type family IsElem' a s :: Constraint
110--
110--
111-- >>> ok (Proxy :: Proxy (IsElem ("bye" :> Get '[JSON] Int) SampleAPI))
111-- >>> ok (Proxy :: Proxy (IsElem ("bye" :> Get '[JSON] Int) SampleAPI))
112-- ...
112-- ...
113−-- ... Could not deduce...
113+-- ... Could not ...
114-- ...
114-- ...
115--
115--
116-- An endpoint is considered within an api even if it is missing combinators
116-- An endpoint is considered within an api even if it is missing combinators
@@ -151,7 +151,7 @@ type family IsElem endpoint api :: Constraint where
151--
151--
152-- >>> ok (Proxy :: Proxy (IsSubAPI (SampleAPI :<|> Get '[JSON] Int) SampleAPI))
152-- >>> ok (Proxy :: Proxy (IsSubAPI (SampleAPI :<|> Get '[JSON] Int) SampleAPI))
153-- ...
153-- ...
154−-- ... Could not deduce...
154+-- ... Could not ...
155-- ...
155-- ...
156--
156--
157-- This uses @IsElem@ for checking; thus the note there applies here.
157-- This uses @IsElem@ for checking; thus the note there applies here.
@@ -174,7 +174,7 @@ type family AllIsElem xs api :: Constraint where
174--
174--
175-- >>> ok (Proxy :: Proxy (IsIn (Get '[JSON] Int) (Header "h" Bool :> Get '[JSON] Int)))
175-- >>> ok (Proxy :: Proxy (IsIn (Get '[JSON] Int) (Header "h" Bool :> Get '[JSON] Int)))
176-- ...
176-- ...
177−-- ... Could not deduce...
177+-- ... Could not ...
178-- ...
178-- ...
179type family IsIn (endpoint :: *) (api :: *) :: Constraint where
179type family IsIn (endpoint :: *) (api :: *) :: Constraint where
180 IsIn e (sa :<|> sb) = Or (IsIn e sa) (IsIn e sb)
180 IsIn e (sa :<|> sb) = Or (IsIn e sa) (IsIn e sb)
servant/src/Servant/API/UVerb.hs+3
@@ -38,6 +38,7 @@ import GHC.TypeLits (Nat)
38import Network.HTTP.Types (Status, StdMethod)
38import Network.HTTP.Types (Status, StdMethod)
39import Servant.API.ContentTypes (JSON, PlainText, FormUrlEncoded, OctetStream, NoContent, MimeRender(mimeRender), MimeUnrender(mimeUnrender))
39import Servant.API.ContentTypes (JSON, PlainText, FormUrlEncoded, OctetStream, NoContent, MimeRender(mimeRender), MimeUnrender(mimeUnrender))
40import Servant.API.Status (KnownStatus, statusVal)
40import Servant.API.Status (KnownStatus, statusVal)
41+import Servant.API.ResponseHeaders (Headers)
41import Servant.API.UVerb.Union
42import Servant.API.UVerb.Union
42
43
43class KnownStatus (StatusOf a) => HasStatus (a :: *) where
44class KnownStatus (StatusOf a) => HasStatus (a :: *) where
@@ -86,6 +87,8 @@ newtype WithStatus (k :: Nat) a = WithStatus a
86instance KnownStatus n => HasStatus (WithStatus n a) where
87instance KnownStatus n => HasStatus (WithStatus n a) where
87 type StatusOf (WithStatus n a) = n
88 type StatusOf (WithStatus n a) = n
88
89
90+instance HasStatus a => HasStatus (Headers ls a) where
91+ type StatusOf (Headers ls a) = StatusOf a
89
92
90-- | A variant of 'Verb' that can have any of a number of response values and status codes.
93-- | A variant of 'Verb' that can have any of a number of response values and status codes.
91--
94--
servant/src/Servant/API/UVerb/Union.hs+2−2
@@ -128,9 +128,9 @@ type DuplicateElementError (rs :: [k]) =
128 ':$$: 'Text " " ':<>: 'ShowType rs
128 ':$$: 'Text " " ':<>: 'ShowType rs
129
129
130type family Elem (x :: k) (xs :: [k]) :: Bool where
130type family Elem (x :: k) (xs :: [k]) :: Bool where
131+ Elem x (x ': _) = 'True
132+ Elem x (_ ': xs) = Elem x xs
131 Elem _ '[] = 'False
133 Elem _ '[] = 'False
132− Elem x (x' ': xs) =
133− If (x == x') 'True (Elem x xs)
134
134
135type family Unique xs :: Constraint where
135type family Unique xs :: Constraint where
136 Unique xs = If (Nubbed xs == 'True) (() :: Constraint) (TypeError (DuplicateElementError xs))
136 Unique xs = If (Nubbed xs == 'True) (() :: Constraint) (TypeError (DuplicateElementError xs))
servant/src/Servant/API/Verbs.hs+2−1
@@ -128,7 +128,8 @@ type DeleteNoContent = NoContentVerb 'DELETE
128type PatchNoContent = NoContentVerb 'PATCH
128type PatchNoContent = NoContentVerb 'PATCH
129-- | 'PUT' with 204 status code.
129-- | 'PUT' with 204 status code.
130type PutNoContent = NoContentVerb 'PUT
130type PutNoContent = NoContentVerb 'PUT
131−
131+-- | 'HEAD' with 204 status code.
132+type HeadNoContent = NoContentVerb 'HEAD
132
133
133-- ** 205 Reset Content
134-- ** 205 Reset Content
134--
135--
servant/src/Servant/API/WithResource.hsnew file+3
@@ -0,0 +1,3 @@
1+module Servant.API.WithResource (WithResource) where
2+
3+data WithResource res
servant/src/Servant/Links.hs+26−5
@@ -1,5 +1,6 @@
1{-# LANGUAGE AllowAmbiguousTypes #-}
1{-# LANGUAGE AllowAmbiguousTypes #-}
2{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE CPP #-}
3{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE FlexibleInstances #-}
@@ -91,7 +92,7 @@
91-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] NoContent)
92-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] NoContent)
92-- >>> safeLink api bad_link
93-- >>> safeLink api bad_link
93-- ...
94-- ...
94−-- ...Could not deduce...
95+-- ...Could not ...
95-- ...
96-- ...
96--
97--
97-- This error is essentially saying that the type family couldn't find
98-- This error is essentially saying that the type family couldn't find
@@ -174,7 +175,7 @@ import Servant.API.NamedRoutes
174import Servant.API.QueryParam
175import Servant.API.QueryParam
175 (QueryFlag, QueryParam', QueryParams)
176 (QueryFlag, QueryParam', QueryParams)
176import Servant.API.Raw
177import Servant.API.Raw
177− (Raw)
178+ (Raw, RawM)
178import Servant.API.RemoteHost
179import Servant.API.RemoteHost
179 (RemoteHost)
180 (RemoteHost)
180import Servant.API.ReqBody
181import Servant.API.ReqBody
@@ -192,7 +193,11 @@ import Servant.API.Verbs
192 (Verb, NoContentVerb)
193 (Verb, NoContentVerb)
193import Servant.API.WithNamedContext
194import Servant.API.WithNamedContext
194 (WithNamedContext)
195 (WithNamedContext)
196+import Servant.API.WithResource
197+ (WithResource)
195import Web.HttpApiData
198import Web.HttpApiData
199+import Data.Kind
200+ (Type)
196
201
197-- | A safe link datatype.
202-- | A safe link datatype.
198-- The only way of constructing a 'Link' is using 'safeLink', which means any
203-- The only way of constructing a 'Link' is using 'safeLink', which means any
@@ -555,6 +560,10 @@ instance HasLink sub => HasLink (WithNamedContext name context sub) where
555 type MkLink (WithNamedContext name context sub) a = MkLink sub a
560 type MkLink (WithNamedContext name context sub) a = MkLink sub a
556 toLink toA _ = toLink toA (Proxy :: Proxy sub)
561 toLink toA _ = toLink toA (Proxy :: Proxy sub)
557
562
563+instance HasLink sub => HasLink (WithResource res :> sub) where
564+ type MkLink (WithResource res :> sub) a = MkLink sub a
565+ toLink toA _ = toLink toA (Proxy :: Proxy sub)
566+
558instance HasLink sub => HasLink (RemoteHost :> sub) where
567instance HasLink sub => HasLink (RemoteHost :> sub) where
559 type MkLink (RemoteHost :> sub) a = MkLink sub a
568 type MkLink (RemoteHost :> sub) a = MkLink sub a
560 toLink = simpleToLink (Proxy :: Proxy sub)
569 toLink = simpleToLink (Proxy :: Proxy sub)
@@ -580,6 +589,10 @@ instance HasLink Raw where
580 type MkLink Raw a = a
589 type MkLink Raw a = a
581 toLink toA _ = toA
590 toLink toA _ = toA
582
591
592+instance HasLink RawM where
593+ type MkLink RawM a = a
594+ toLink toA _ = toA
595+
583instance HasLink (Stream m status fr ct a) where
596instance HasLink (Stream m status fr ct a) where
584 type MkLink (Stream m status fr ct a) r = r
597 type MkLink (Stream m status fr ct a) r = r
585 toLink toA _ = toA
598 toLink toA _ = toA
@@ -647,12 +660,20 @@ simpleToLink _ toA _ = toLink toA (Proxy :: Proxy sub)
647-- >>> import Data.Text (Text)
660-- >>> import Data.Text (Text)
648
661
649-- Erroring instance for 'HasLink' when a combinator is not fully applied
662-- Erroring instance for 'HasLink' when a combinator is not fully applied
650−instance TypeError (PartialApplication HasLink arr) => HasLink ((arr :: a -> b) :> sub)
663+instance TypeError (PartialApplication
664+#if __GLASGOW_HASKELL__ >= 904
665+ @(Type -> Constraint)
666+#endif
667+ HasLink arr) => HasLink ((arr :: a -> b) :> sub)
651 where
668 where
652− type MkLink (arr :> sub) _ = TypeError (PartialApplication HasLink arr)
669+ type MkLink (arr :> sub) _ = TypeError (PartialApplication (HasLink :: * -> Constraint) arr)
653 toLink = error "unreachable"
670 toLink = error "unreachable"
654
671
655-- Erroring instances for 'HasLink' for unknown API combinators
672-- Erroring instances for 'HasLink' for unknown API combinators
656−instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub HasLink ty) => HasLink (ty :> sub)
673+instance {-# OVERLAPPABLE #-} TypeError (NoInstanceForSub
674+#if __GLASGOW_HASKELL__ >= 904
675+ @(Type -> Constraint)
676+#endif
677+ HasLink ty) => HasLink (ty :> sub)
657
678
658instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasLink api)) => HasLink api
679instance {-# OVERLAPPABLE #-} TypeError (NoInstanceFor (HasLink api)) => HasLink api
servant/src/Servant/Test/ComprehensiveAPI.hs+1
@@ -72,6 +72,7 @@ type ComprehensiveAPIWithoutStreamingOrRaw' endpoint =
72 :<|> "description" :> Description "foo" :> GET
72 :<|> "description" :> Description "foo" :> GET
73 :<|> "alternative" :> ("left" :> GET :<|> "right" :> GET)
73 :<|> "alternative" :> ("left" :> GET :<|> "right" :> GET)
74 :<|> "fragment" :> Fragment Int :> GET
74 :<|> "fragment" :> Fragment Int :> GET
75+ :<|> "resource" :> WithResource Int :> GET
75 :<|> endpoint
76 :<|> endpoint
76
77
77type ComprehensiveAPIWithoutStreamingOrRaw = ComprehensiveAPIWithoutStreamingOrRaw' EmptyEndpoint
78type ComprehensiveAPIWithoutStreamingOrRaw = ComprehensiveAPIWithoutStreamingOrRaw' EmptyEndpoint
servant/src/Servant/Types/SourceT.hs+4−1
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DeriveFunctor #-}
2{-# LANGUAGE DeriveFunctor #-}
2{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
3{-# LANGUAGE RankNTypes #-}
4{-# LANGUAGE RankNTypes #-}
@@ -154,8 +155,10 @@ instance (Applicative m, Show1 m, Show a) => Show (StepT m a) where
154-- | >>> lift [1,2,3] :: StepT [] Int
155-- | >>> lift [1,2,3] :: StepT [] Int
155-- Effect [Yield 1 Stop,Yield 2 Stop,Yield 3 Stop]
156-- Effect [Yield 1 Stop,Yield 2 Stop,Yield 3 Stop]
156--
157--
158+#if !MIN_VERSION_transformers(0,6,0)
157instance MonadTrans StepT where
159instance MonadTrans StepT where
158 lift = Effect . fmap (`Yield` Stop)
160 lift = Effect . fmap (`Yield` Stop)
161+#endif
159
162
160instance MFunctor StepT where
163instance MFunctor StepT where
161 hoist f = go where
164 hoist f = go where
@@ -319,7 +322,7 @@ fromActionStep stop action = loop where
319-- | Read file.
322-- | Read file.
320--
323--
321-- >>> foreach fail BS.putStr (readFile "servant.cabal")
324-- >>> foreach fail BS.putStr (readFile "servant.cabal")
322−-- cabal-version: >=1.10
325+-- cabal-version: 2.2
323-- name: servant
326-- name: servant
324-- ...
327-- ...
325--
328--
servant/test/Servant/API/ContentTypesSpec.hs+11
@@ -33,6 +33,8 @@ import Data.String.Conversions
33import qualified Data.Text as TextS
33import qualified Data.Text as TextS
34import qualified Data.Text.Encoding as TextSE
34import qualified Data.Text.Encoding as TextSE
35import qualified Data.Text.Lazy as TextL
35import qualified Data.Text.Lazy as TextL
36+import Control.Exception
37+ (evaluate)
36import GHC.Generics
38import GHC.Generics
37import Test.Hspec
39import Test.Hspec
38import Test.QuickCheck
40import Test.QuickCheck
@@ -78,6 +80,15 @@ spec = describe "Servant.API.ContentTypes" $ do
78 it "has mimeUnrender reverse mimeRender for valid top-level json " $ do
80 it "has mimeUnrender reverse mimeRender for valid top-level json " $ do
79 property $ \x -> mimeUnrender p (mimeRender p x) == Right (x::SomeData)
81 property $ \x -> mimeUnrender p (mimeRender p x) == Right (x::SomeData)
80
82
83+ describe "The NoContent Content-Type type" $ do
84+ let p = Proxy :: Proxy '[JSON]
85+
86+ it "does not render any content" $
87+ allMimeRender p NoContent `shouldSatisfy` (all (BSL8.null . snd))
88+
89+ it "evaluates the NoContent value" $
90+ evaluate (allMimeRender p (undefined :: NoContent)) `shouldThrow` anyErrorCall
91+
81 describe "The PlainText Content-Type type" $ do
92 describe "The PlainText Content-Type type" $ do
82 let p = Proxy :: Proxy PlainText
93 let p = Proxy :: Proxy PlainText
83
94
servant/test/Servant/API/ResponseHeadersSpec.hs+11
@@ -2,10 +2,14 @@
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Servant.API.ResponseHeadersSpec where
3module Servant.API.ResponseHeadersSpec where
4
4
5+import Data.Proxy
6+import GHC.TypeLits
5import Test.Hspec
7import Test.Hspec
6
8
9+import Servant.API.ContentTypes
7import Servant.API.Header
10import Servant.API.Header
8import Servant.API.ResponseHeaders
11import Servant.API.ResponseHeaders
12+import Servant.API.UVerb
9
13
10spec :: Spec
14spec :: Spec
11spec = describe "Servant.API.ResponseHeaders" $ do
15spec = describe "Servant.API.ResponseHeaders" $ do
@@ -28,3 +32,10 @@ spec = describe "Servant.API.ResponseHeaders" $ do
28 it "does not add a header" $ do
32 it "does not add a header" $ do
29 let val = noHeader 5 :: Headers '[Header "test" Int] Int
33 let val = noHeader 5 :: Headers '[Header "test" Int] Int
30 getHeaders val `shouldBe` []
34 getHeaders val `shouldBe` []
35+
36+ describe "HasStatus Headers" $ do
37+
38+ it "gets the status from the underlying value" $ do
39+ natVal (Proxy :: Proxy (StatusOf (Headers '[Header "first" Int] NoContent))) `shouldBe` 204
40+ natVal (Proxy :: Proxy (StatusOf (Headers '[Header "first" Int] (WithStatus 503 ())))) `shouldBe` 503
41+
stack.yaml+1
@@ -11,6 +11,7 @@ packages:
11- servant-conduit
11- servant-conduit
12- servant-machines/
12- servant-machines/
13- servant-pipes/
13- servant-pipes/
14+- servant-swagger/
14
15
15# allow-newer: true # ignores all bounds, that's a sledgehammer
16# allow-newer: true # ignores all bounds, that's a sledgehammer
16# - doc/tutorial/
17# - doc/tutorial/