Compare commits

..

739 commits

Author SHA1 Message Date
Intolerable
a2e003367d
Add HasStatus instance for Headers (that defers StatusOf to underlying value) (#1649)
* Add HasStatus instance for Headers (that defers StatusOf to underlying value)

* changelog.d/1649
2023-02-14 23:28:57 +01:00
ˌbodʲɪˈɡrʲim
b3214eac38
Require wai >= 3.2.2.1 (#1644) 2023-01-28 13:02:36 +01:00
Jan Hrcek
f71953e63d
Fix haddock code examples in HasClient (#1640) 2023-01-28 13:02:11 +01:00
Théophile Choutri
c382a1f34e
Allow resourcet-1.3 in servant-server and servant-conduit (#1632) 2023-01-18 09:44:11 +01:00
Daan Rijks
2daae80ea8
Add (basic) API docs for ServerT (#1573) 2023-01-09 17:05:08 +01:00
Torgeir Strand Henriksen
a22600979a
Add Functor instance to AuthHandler. (#1638) 2022-12-30 12:56:52 +01:00
Théophile Choutri
b8675c0924
Provisionally disable the Stack CI, it's too flaky. (#1639) 2022-12-29 19:25:58 +01:00
andremarianiello
751350ba9e
WithResource combinator for Servant-managed resources (#1630) 2022-12-29 19:00:47 +01:00
Guillaume Bouchard
a4194dc490
feat: Polymorphic Elem for Union (#1637)
Close https://github.com/haskell-servant/servant/issues/1590
2022-12-23 09:42:52 +01:00
nbacquey
6392dce4bf
Document CaptureHint in Capture[All]Router (#1634)
Co-authored-by: Nicolas BACQUEY <nicolas.bacquey@tweag.io>
2022-12-08 09:20:53 +01:00
Janus Troelsen
8f081bd9ad
Allow mtl-2.3, require jose-0.10 (#1627) 2022-11-17 16:58:52 +01:00
romes
ad25e98e19
Handle Cookies correctly for RunStreamingClient (#1606) 2022-11-03 09:46:49 +01:00
Maxim Koltsov
0fc6e395cb
Remove allow-newer for postgresql-simple (#1625)
Upstream has released updated versions.
2022-10-31 23:59:35 +03:00
Maxim Koltsov
58aa0d1c0c
Merge pull request #1621 from haskell-servant/maksbotan/version-up
Version up for servant, servant-server
2022-10-28 01:26:00 +03:00
Maxim Koltsov
18bc2cf314
Version up for servant, servant-server 2022-10-27 21:26:36 +02:00
Maxim Koltsov
d5b9cbf634
Merge pull request #1592 from TeofilC/ghc-9.4
Support GHC-9.4
2022-10-27 22:14:26 +03:00
Teo Camarasu
ff135e868b Add flags to cabal.project to allow building with GHC-9.4 2022-10-27 13:05:51 +01:00
Teo Camarasu
86c61c6dbd Update doctest to be compatible with newer GHC 2022-10-27 13:05:51 +01:00
Teo Camarasu
3f6886ad2d Bump depedency bounds 2022-10-27 13:05:38 +01:00
Teo Camarasu
53c132173c Bump http-api-data bounds 2022-10-27 13:05:05 +01:00
Teo Camarasu
a445fbafd6 Use CPP to avoid errors with old GHC from TypeApplications in class instance 2022-10-27 13:05:05 +01:00
Teo Camarasu
52f76ea722 Add GHC-9.4 to workflow 2022-10-27 13:05:05 +01:00
Teo Camarasu
4627683a64 Fix TypeError for GHC-9.4
In GHC-9.4 the typechecker changed requiring more annotations in positions like this. See https://gitlab.haskell.org/ghc/ghc/-/wikis/migration/9.4#ambiguous-types-containing-a-typeerror and https://gitlab.haskell.org/ghc/ghc/-/issues/21149
2022-10-18 10:45:21 +01:00
l-epple
e4650de303
Allow lens 5.2 (#1607) 2022-10-02 17:21:43 +02:00
Felix Yan
2323906080
Allow hspec 2.10 (#1609)
Builds fine and all tests pass.
2022-09-07 07:31:58 +02:00
Maxim Koltsov
f0e2316895
Merge pull request #1596 from haskell-servant/maksbotan/servant-auth-ghc9.2
servant-auth-swagger: allow base-4.16
2022-07-17 21:11:45 +03:00
Maxim Koltsov
43c57332dd
servant-auth-swagger: buildable on GHC 9 2022-07-17 20:48:52 +03:00
Maxim Koltsov
1833ef0d6e
servant-auth-swagger: allow base-4.16 2022-07-17 20:01:25 +03:00
Bart Schuurmans
489cbd59f4
servant-client: Run ClientEnv's makeClientRequest in IO (#1595)
* servant-client: Run ClientEnv's makeClientRequest in IO

* Add changelog.d entry for #1595
2022-07-01 13:25:13 +02:00
Ian Shipman
1fba9dc604
Only add a ? when query string is nonempty (#1589)
* Only add a ? when query string is nonempty

* Adds changelog entry
2022-05-16 16:50:10 +02:00
Gaël Deest
8ef5021a5f
Merge pull request #1588 from LightAndLight/master
Add HasSwagger instance for NamedRoutes
2022-05-13 07:41:12 +02:00
Tom Sydney Kerckhove
036102af58
Evaluate NoContent before (not) rendering it. (#1587)
* Evaluate NoContent before rendering it, so it shows up as covered in coverage reports

* failing test as well

* test that NoContent gets rendered if it is not an exception

Co-authored-by: Tom Sydney Kerckhove <syd@cs-syd.eu>
2022-05-04 14:40:26 +02:00
Isaac Elliott
59b5fe67cd servant-swagger: clean up imports 2022-05-03 11:43:30 +10:00
Isaac Elliott
ae8e1e6003 servant-swagger: tag NamedRoutes endpoints with datatype name 2022-05-03 11:43:27 +10:00
Isaac Elliott
cb310b8294 servant-swagger: add HasSwagger instance for NamedRoutes 2022-05-03 11:43:16 +10:00
Julian Arni
5e1569e9e2
Merge pull request #1580 from haskell-servant/jkarni/servant-auth-io-keyset
Allow IO in JWTSettings' validationKeys
2022-04-23 18:17:00 -03:00
Julian K. Arni
4e8fb045e2 Review fix 2022-04-20 21:07:08 +02:00
Julian K. Arni
4cc714d654 Changelog entry 2022-04-20 21:07:08 +02:00
Julian K. Arni
3006e90126 Allow IO in JWTSettings' validationKeys 2022-04-20 21:07:08 +02:00
Gaël Deest
c48a6702b7
Merge pull request #1582 from haskell-servant/named-routes-servant-docs
Add support for NamedRoutes in servant-docs
2022-04-19 13:13:50 +02:00
Gaël Deest
9c81b4927a Add support for NamedRoutes in servant-docs 2022-04-19 12:51:31 +02:00
Gaël Deest
117a2cc5e1
Merge pull request #1583 from haskell-servant/hspec-no-color
Disable hspec colored output in servant-swagger doctests
2022-04-19 12:41:15 +02:00
Gaël Deest
78280dc267 Disable hspec colored output in servant-swagger doctests
Colored output is the default since hspec 2.9.5.

This causes CI failures due to terminal escaping characters when running
the doctests on GitHub Actions.
2022-04-19 11:16:03 +02:00
Alp
c19ed0fb92
Major bound for servant-server's dependency on servant (#1574)
Reflecting a revision made on hackage for servant-server 0.19.1
2022-03-30 02:10:54 +02:00
Shea Levy
658585a7cd
Derive MonadMask for ClientM (#1572) 2022-03-26 17:03:01 +01:00
Gaël Deest
65de6f701c
Merge pull request #1556 from nbacquey/router_layout_captures
Display capture hints in router layout
2022-03-25 10:42:33 +01:00
Nicolas BACQUEY
a19cb84a0e Update changelog 2022-03-24 16:43:27 +01:00
Nicolas BACQUEY
9d66e16706 Add spec for serverLayout 2022-03-23 14:30:45 +01:00
Nicolas BACQUEY
77b92d0d7d Display capture hints in router layout
This commit introduces a `CaptureHint` type, which is passed as an extra
argument to the `CaptureRouter` and `CaptureAllRouter` constructors for
the `Router'` type.
`CaptureHint` values are then used in `routerLayout`, to display the
name and "type" of captured values (single or list), instead of just
"<capture>" previously.

N.B.:
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.
2022-03-23 14:30:45 +01:00
Maxim Koltsov
f5a91d20e1
Merge pull request #1568 from haskell-servant/maksbotan/stackage-deps
Allow hspec-2.9, lens-aeson-1.2
2022-03-22 23:43:22 +01:00
Maxim Koltsov
dd29f25f77
Allow lens-aeson 1.2 2022-03-22 23:22:02 +01:00
Maxim Koltsov
04f59c012b
Require servant-0.18.2 in servant-swagger
This version of servant adds Fragment, which servant-swagger adds
instance for.
2022-03-22 23:10:05 +01:00
Maxim Koltsov
256cec566f
Support hspec >= 2.9 in servant-swagger tests 2022-03-22 22:54:58 +01:00
Gaël Deest
276ca2ed01
Merge pull request #1569 from haskell-servant/url-encoding
Use toEncodedUrlPiece directly when encoding captures
2022-03-22 14:19:07 +01:00
Gaël Deest
c1c631eaff Add changelog entry 2022-03-22 11:56:18 +01:00
Gaël Deest
0e051ccfdf
Merge pull request #1557 from ysangkok/janus/newer-stack
Use Stack 2.7.5, cleanup allow-newer/CI
2022-03-22 11:36:17 +01:00
Gaël Deest
658217b021 Use toEncodedUrlPiece directly when encoding captures
Current implementation of captures uses the `toUrlPiece` method from the
`ToHttpApiData` typeclass, and encodes the resulting `Text` using `toEncodedUrlPiece`
when appending to the request path.

The problem with this approach is that the instance for `Text` percent-encodes
characters that are perfectly valid in URLs, such as `*`.

This patch makes direct use of `toEncodedUrlPiece`, which lets users implement
encoding according to their needs.

Closes #1511
2022-03-21 17:29:23 +01:00
Gaël Deest
af3dde1b1d
Merge pull request #1566 from haskell-servant/fix-operator-doc
Fix haddock documentation for (//) and (/:)
2022-03-21 16:14:47 +01:00
Maxim Koltsov
ced5f1a655
Allow hspec-2.9 2022-03-21 15:44:10 +01:00
Maxim Koltsov
626e1c3a7c
Relax more deps for Stackage (#1567) 2022-03-21 17:18:08 +03:00
Gaël Deest
0c80bc8f8e Fix haddock documentation for (//) and (/:)
The examples for these two operators weren't displayed properly due to invalid Haddock markup.
2022-03-21 14:18:49 +01:00
Maxim Koltsov
d52c5d08a0
servant-server 0.19.1 2022-03-21 14:13:52 +01:00
Maxim Koltsov
89b66a3634
Merge pull request #1555 from ysangkok/janus/ghc-92
Allow GHC 9.2 for all packages
2022-03-21 13:58:48 +01:00
Gaël Deest
3370b75622
Merge pull request #1565 from haskell-servant/re-export
Re-export Servant.API.Generic in Servant.API
2022-03-21 13:57:40 +01:00
Gaël Deest
9a99ef9a0b Re-export Servant.API.Generic in Servant.API 2022-03-21 13:31:33 +01:00
Maxim Koltsov
408352320e
Remove obsolete allow-newer 2022-03-21 11:45:49 +01:00
Janus Troelsen
010e6a72af Disable curl-mock for 9.2 because of generic-arbitrary 2022-03-13 20:35:40 -06:00
Janus Troelsen
39898676a8 Enable all packages on GHC 9.2 2022-03-13 19:58:24 -06:00
Janus Troelsen
bbd82a736f Use Stack 2.7.5, cleanup 2022-03-09 12:58:50 -06:00
Janus Troelsen
17e3eb1041 Allow GHC 9.2 for compatible packages 2022-03-08 08:59:35 -06:00
Gaël Deest
de923fc887
Merge pull request #1554 from ysangkok/repl-doctest
Use cabal-install to invoke doctest
2022-03-08 09:16:19 +01:00
Janus Troelsen
222ccf107c Use cabal-install to invoke doctest 2022-03-08 01:01:37 -06:00
Giorgio Marinelli
d05da71f09
Export encoding function for a query parameter value (#1549) 2022-03-01 15:22:25 +01:00
Marco Perone
cedab6572d
fix broken links (#1548) 2022-03-01 09:34:45 +01:00
Gaël Deest
15b364ae93
Merge pull request #1541 from mjdominus/master
Update documentation
2022-02-28 09:50:17 +01:00
Gaël Deest
8fccfccae0
Merge pull request #1546 from hasufell/PR/hasufell/issue-1545/monad-fail
Add `MonadFail` instance for `Handler` wrt #1545
2022-02-28 09:47:33 +01:00
Julian Ospald
181e51db8a
Add MonadFail instance for Handler wrt #1545 2022-02-26 22:31:56 +01:00
Mark Jason Dominus (陶敏修)
0e4d02ae75 Update copyright notice from 2018 to 2022 2022-02-25 14:44:13 -05:00
Mark Jason Dominus (陶敏修)
b4c4131778 Update error message in Makefile
The file this message refers to was renamed in
commit 53b3b939e4.
2022-02-25 14:44:13 -05:00
Mark Jason Dominus (陶敏修)
6d5c3023ce Discuss ghcup in tutorial installation instructions
The instructions as written will not work on Ubuntu systems,
which provide an extremely out-of-date Haskell toolchain.

Addresses issue https://github.com/haskell-servant/servant/issues/1540

https://github.com/haskell-servant/servant/issues/1540
2022-02-25 14:44:12 -05:00
Caroline GAUDREAU
7ef9730f77
Merge pull request #1538 from akhesaCaro/reverting
Reverting NamedRoutes cookbook
2022-02-18 12:37:05 +01:00
akhesaCaro
6da8488f9b Revert "removing Generic cookbook in favour of NamedRoutes"
This reverts commit 34aed1d289.
2022-02-18 12:14:28 +01:00
akhesaCaro
f4cd56446b Revert "introducing NamedRoutes cookbook"
This reverts commit 5c80214351.
2022-02-18 12:13:09 +01:00
Caroline GAUDREAU
50355d0125
Merge pull request #1534 from akhesaCaro/cookbook_namedRoutes
Cookbook named routes
2022-02-18 11:41:19 +01:00
akhesaCaro
34aed1d289 removing Generic cookbook in favour of NamedRoutes 2022-02-18 11:08:43 +01:00
akhesaCaro
5c80214351 introducing NamedRoutes cookbook 2022-02-18 11:08:36 +01:00
Gaël Deest
009dc06e76
Merge pull request #1535 from ysangkok/remove-unnecessary-constraints-and-allow-newer
Remove unnecessary constraint/allow-newer
2022-02-16 22:51:59 +01:00
Janus Troelsen
e2a9165229 Remove unnecessary constraint/allow-newer 2022-02-15 09:54:17 -06:00
Gaël Deest
d35b3e9b70
Merge pull request #1529 from purefunsolutions/fix-servant-client-ghcjs-for-servant-0.19
Fix servant-client-ghcjs for servant 0.19
2022-02-14 16:39:00 +01:00
Gaël Deest
002fa2107a
Merge pull request #1531 from gdeest/servant-auth-named-routes
servant-auth-server: Support NamedRoutes
2022-02-14 14:57:19 +01:00
Gaël Deest
bd9151b9de servant-auth-server: Support NamedRoutes
Trying to use `NamedRoutes` with `servant-auth-server` currently results
in hideous error messages such as:

```
app/Main.hs:50:7: error:
    • No instance for (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookies
                         ('Servant.Auth.Server.Internal.AddSetCookie.S
                            ('Servant.Auth.Server.Internal.AddSetCookie.S
                               'Servant.Auth.Server.Internal.AddSetCookie.Z))
                         (AdminRoutes (Servant.Server.Internal.AsServerT Handler))
                         (ServerT
                            (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookieApi
                               (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookieApi
                                  (NamedRoutes AdminRoutes)))
                            Handler))
        arising from a use of 'serveWithContext'
    • In the expression: serveWithContext (Proxy @API) ctx RootAPI {..}
```

This is because we didn't teach it how to recurse along `NamedRoutes`
trees and sprinkle headers at the tip of each branch.

This commit adds a test case and fixes the issue. In the process, it
also implements `ThrowAll` for `NamedRoutes`, which was necessary for
the test to run, and should also prove convenient for users.
2022-02-14 14:28:46 +01:00
Mika Tammi
17b55634b3
servant-client-ghcjs: Fix performRequest function
Fix performRequest function to be compatible with the latest
servant-client-core RunClient typeclass
2022-02-11 20:55:34 +02:00
Mika Tammi
3158809631
servant-client-ghcjs: Bump base max-bound 2022-02-11 20:22:57 +02:00
Gaël Deest
cdd7c34add
Merge pull request #1526 from ysangkok/master
Allow newer hashable, lens, text
2022-02-07 10:00:58 +01:00
Gaël Deest
67322d8ab8
Merge pull request #1525 from k0001/fix-9.2.1
servant-server: Fix build on GHC 9.2.1
2022-02-07 09:57:23 +01:00
Janus Troelsen
67da8514a0 Allow newer hashable, lens, text 2022-02-06 16:12:25 -06:00
Renzo Carbonara
61d0d14b5c servant-server: Fix build on GHC 9.2.1
The issue is similar to the one in #1513:

```
src/Servant/Server/Internal.hs:824:10: error:
    • Uninferrable type variable k0 in
      type family equation right-hand side: (TypeError ...)
    • In the type instance declaration for ‘ServerT’
      In the instance declaration for
        ‘HasServer ((arr :: a -> b) :> sub) context’
    |
824 |     type ServerT (arr :> sub) _ = TypeError (PartialApplication HasServer arr)
    |
```

This fix is similar to the one in #1514.
2022-02-04 14:34:12 +02:00
Sven Tennie
a8f1a7603f
Update docs: #haskell-servant is now on libera.chat (#1503) 2022-02-03 12:40:00 +01:00
Gaël Deest
78034cd2b3
Merge pull request #1522 from peterbecich/github-actions-updates
minor updates to GitHub Actions
2022-02-03 10:20:23 +01:00
Clément Delafargue
6f12e38698
Fix NamedRoutes example in 0.19 changelog (#1523) 2022-02-03 09:56:19 +01:00
Peter Becich
9a3fd77a3a
minor updates to GitHub Actions 2022-02-02 23:50:10 -08:00
Gaël Deest
e14f445e2a
Merge pull request #1521 from gdeest/minor-releases
servant-auth 0.4.0.0 -> 0.4.1.0, servant-auth-server 0.4.6.0 -> 0.4.7.0
2022-02-02 16:14:21 +01:00
Gaël Deest
4caa1f563b servant-auth 0.4.0.0 -> 0.4.1.0, servant-auth-server 0.4.6.0 -> 0.4.7.0 2022-02-02 15:54:31 +01:00
Gaël Deest
e1b59dbb31
Merge pull request #1519 from haskell-servant/prepare-0.19
Changelog tweaks + servant-http-streams / servant-docs bump
2022-02-01 12:42:34 +01:00
Gaël Deest
b17d018d3f Changelog tweaks + servant-http-streams / servant-docs bump 2022-02-01 12:29:31 +01:00
Gaël Deest
e98ae8adba
Merge pull request #1517 from haskell-servant/prepare-0.19
Prepare 0.19 release
2022-02-01 10:28:15 +01:00
Gaël Deest
e4945740aa Prepare 0.19 release 2022-02-01 10:17:03 +01:00
Gaël Deest
7a770b5a1e
Merge pull request #1514 from guibou/fix_ghc92_build
Fix GHC 9.2 build
2022-01-25 11:42:10 +01:00
Guillaume Bouchard
22d5790e73 Fix GHC 9.2 build
Close #1513.

GHC 9.2 needs explicit kind signature here, I don't really understand
why.

This kind signature is correct and not too restritive, because `HasLink`
is technically defined `class HasLink endpoint` which means that it is
infered as `k -> Constraint`. In the instance signature, we have
`HasLink ((arr :: a -> b) :> sub)`, so here the `k` is the same kind as
the one of `:>` which is not polykinded.
2022-01-24 17:14:44 +01:00
Gaël Deest
75db4a5327
Merge pull request #1486 from haskell-servant/type-errors
Custom errors for HasClient, HasServer
2022-01-18 17:16:37 +01:00
Gaël Deest
75cb9ac246 Add comment about slightly incorrect error message 2022-01-18 16:25:11 +01:00
Gaël Deest
aab7e0d5dd Custom errors for HasClient, HasServer 2022-01-18 16:25:05 +01:00
Gaël Deest
3493d135f0
Merge pull request #1508 from haskell-servant/fix-servant-swagger-build
Fix servant-swagger Cabal
2022-01-18 11:15:10 +01:00
Gaël Deest
e8c301afc9 Add servant-swagger to stack.yaml 2022-01-18 11:07:38 +01:00
Gaël Deest
b56d681fde Relax doctest lower bound 2022-01-18 11:07:38 +01:00
Gaël Deest
b33442423e Re-adding Cabal-the-library as a dep
Fixes #1507.
2022-01-18 11:07:17 +01:00
Sven Tennie
c388c5e82c
Add HeadNoContent to Servant.API.Verbs (#1502)
As the head method isn't allowed to contain any response body, no
general Head Verb is added. (This may easily lead to wrong usages...)

(https://httpwg.org/specs/rfc7231.html#HEAD)
2022-01-06 13:02:57 +01:00
Matthieu Coudron
73c87bc2bc
bumped cabal-version field (#1498)
* bumped cabal-version field

Cabal supports two types of licenses, native and SPDX, which can be seen here hackage.haskell.org/package/Cabal-3.6.2.0/docs/Distribution-Types-PackageDescription.html#v:licenseRaw

Several packages use BSD-3-Clause as a license, in conjonction with cabal-version: >=1.10 which cabal parses as Right (UnknownLicense "BSD-3").
If I change teh cabal-version to cabal-version: 2.2 , cabal correctly identifdies the license License (ELicense (ELicenseId BSD_3_Clause)).

* changed license from cabal to spdx format

aka BSD3 -> BSD-3-Clause: next cabal may deprecate the old format
2022-01-04 22:06:23 +01:00
Giorgio Marinelli
29d2553e74
Derive HasClient good response status from Verb status (#1469) 2021-12-09 10:09:18 +01:00
antoine-fl
cb294aa2b3
Fix Request's Show instance (#1492) 2021-12-01 19:16:59 +01:00
Théophile Choutri
a975cfc361
Add details about AddHeaders instances (#1490)
* Add details about the instances of AddHeader

Also:

* Cleanup of extensions and imports
2021-11-30 23:52:06 +01:00
Caroline GAUDREAU
9a3979926d
Merge pull request #1475 from akhesaCaro/aeson_2
support Aeson 2
2021-11-26 17:25:56 +01:00
akhesacaro
05ef0dd1d3 Allow using aeson 1 (lax with min-bounds) 2021-11-26 17:14:31 +01:00
akhesacaro
62033db535 servant-auth-swagger: bump servant-swagger and swagger2 2021-11-18 11:56:38 +01:00
akhesacaro
d9d8fa7525 servant-swagger: remove obsolete files 2021-11-18 11:56:38 +01:00
akhesacaro
42ceb3916d changing servant-swagger info 2021-11-18 11:56:38 +01:00
akhesacaro
bcb484774e servant-swagger: bump aeson and cabal (aeson > 2) 2021-11-18 11:56:38 +01:00
akhesacaro
39fb875951 moving servant-swagger into the main servant repo 2021-11-18 11:56:38 +01:00
akhesacaro
efffc70919 fixing servant-auth (aeson 2.0 bump) 2021-11-18 11:56:38 +01:00
akhesacaro
8af80d35a0 bump jose min and max-bound (aeson 2.0 bump) 2021-11-18 11:56:38 +01:00
akhesacaro
e01188aaad min bound aeson 2 2021-11-18 11:56:32 +01:00
Gaël Deest
3ed24fdd90
Merge pull request #1289 from acondolu/master
Better errors for partially applied combinators
2021-11-18 10:51:30 +01:00
Gaël Deest
0e41e37c93
Merge pull request #1485 from haskell-servant/rename-proof
Rename proof to g{Client,Server,Link}Proof
2021-11-18 10:43:35 +01:00
Gaël Deest
f2bd982eaf Rename proof to g{Client,Server,Link}Proof 2021-11-18 10:25:36 +01:00
Gaël Deest
1bb0282abc
Merge pull request #1388 from gdeest/generic-apis
Improve API for composing generic routes
2021-11-18 10:21:59 +01:00
Gaël Deest
575aa70eca Cleanup 2021-11-18 10:11:45 +01:00
Gaël Deest
d81c8d9911 Add parameter-supplying operator
Renamed `(/:)` to `(//)`, and used `(/:)` for supplying parameters to
client functions.

Should close #1442.
2021-11-18 10:11:45 +01:00
Gaël Deest
6718752b4a Add (/:) operator 2021-11-18 10:11:31 +01:00
Gaël Deest
5f8aaec146 Fix client tests 2021-11-18 10:11:31 +01:00
Gaël Deest
fca59556dd Code reorganization
Move `HasServer (NamedRoutes routes)` instance

The instance has been moved to `Servant.Server.Internal`, as the
instances for other combinators. It is necessary so that the instance
can be re-exported from `Servant.Server` without circular imports.

Otherwise, users have to import `Servant.Server.Generic` manually ;
forgetting to do so will produce confusing error messages about the
missing instance.

Move `HasClient (NamedRoutes routes)` instance

Moved so that the instance is made available when importing
`Servant.Client`, avoiding possibly confusing errors when
`Servant.Client.Generic` isn't imported.
2021-11-18 10:09:58 +01:00
Gaël Deest
b033871dfc Implement HasLink instance for NamedRoutes 2021-11-18 10:09:58 +01:00
Gaël Deest
861cd4f997 Exclude quantified constraints code for GHCJS
QuantifiedConstraints isn't available for GHC 8.4 (where our GHCJS
version is still stuck).

We may need to take a drastic decision for GHCJS at some point.
2021-11-18 10:09:58 +01:00
Gaël Deest
5ead291f8d Implementation of HasClient
Follows the same design as `HasServer` in the previous commit.

A test has been added (which incidentally acts as a test for the
HasServer instance).
2021-11-18 10:09:58 +01:00
Gaël Deest
b0b02f1948 Implement HasServer (NamedRoutes routes)
We define `ServerT (NamedRoutes api) m` as `api (AsServerT m)`, so that
the server of an record-defined API is a record of handlers.

The implementation piggy backs on the instance for “vanilla” servant
types with `(:<|>)`, using the `GServantProduct` for converting backd
and forth between the record / vanilla servers.

The main difficulty is that GHC needs to know that this operation is
legit, which can be expressed as the fact that:

```
GToServant (Rep (ServerT (NamedRoutes api))) m ~
ServerT (GToServant (Rep (api AsApi))) m
```

plus a few additional constraints.

This is easy enough for `route`, as we know that `m ~ Handler`. But in
the case of `hoistServerWithContext`, the two involved monads are
unknown ; in other words, this constraint needs to hold `forall m.`

Switching `-XQuantifiedConstraints` on is not sufficient, as our
constraints involve type families (`Rep` and `ServerT`). Our trick is to
use an intermediary typeclass, `GServer`, as a provider of evidence (in
the form of a `Dict`) that our constraints are indeed satisfied for a
particular monad.

The only instance of `GServer` is defined along with it, so it is
practically invisible to users.
2021-11-18 10:09:58 +01:00
Gaël Deest
65e3070cac Add NamedRoutes combinator
Allows users to directly embed APIs defined as records of routes into
vanilla Servant API types.

E.g.:

```haskell
data MyRoutes mode = MyRoutes
  { version :: mode :- Get '[JSON] Int
  , …
  }

type API = "prefix" :> NamedRoutes MyRoutes :<|> …
```

APIs can thus be recursively defined directly with Generic record types.
2021-11-18 10:09:58 +01:00
Gaël Deest
67a37dc3f6 Fix build error on GHC 8.6 2021-11-17 15:29:22 +01:00
Caroline GAUDREAU
04e4de5260
Merge pull request #1357 from SupercedeTech/master
servant-docs: Add support of Pretty modifier for all verbs aliases
2021-11-17 14:42:24 +01:00
Andrea Condoluci
42b7d0eb9b Type-level errors for HasLink for invalid combinators 2021-11-15 21:40:36 +01:00
Théophile Choutri
f3d25bfdb3
Merge pull request #1479 from tchoutri/update-cabal-spdx-identifiers
Change the license value to a valid SPDX identifier
2021-11-01 10:39:54 +01:00
Théophile Choutri
4e4ad495ef Change the license value to a valid SPDX identifier 2021-10-31 22:37:56 +01:00
Maxim Koltsov
043d5a0e90
Merge pull request #1476 from haskell-servant/maksbotan/fix-servant-auth-tests
Fix tests for some servant-auth pkgs on GHC 9
2021-10-31 22:04:49 +01:00
Maxim Koltsov
70f6c49524
Get rid of Unicode in err404 example (#1478)
ServerError field errBody uses ByteString, whose IsString instance kills
Unicode, thus turning example into garbage. Changed it to simple ASCII
string, since Unicode art did not exactly correspond to 404 error
anyway.

Fixes #1371
2021-10-31 14:36:57 +03:00
Théophile Choutri
70b3721537
Merge pull request #1477 from josephcsible/ghc92
Enable FlexibleContexts in Servant.API.ContentTypes
2021-10-31 12:09:04 +01:00
Joseph C. Sible
fea40bd0fc Enable FlexibleContexts in Servant.API.ContentTypes
Starting with GHC 9.2, UndecidableInstances no longer implies FlexibleContexts.
Add this extension where it's needed to make compilation succeed.
2021-10-30 23:26:21 -04:00
Maxim Koltsov
ca6774d797 Update servant-auth cookbook deps 2021-10-30 21:29:17 +02:00
Maxim Koltsov
e2e9ce0596 Enable servant-auth cookbook 2021-10-30 21:26:44 +02:00
Maxim Koltsov
53b1d9d2b6 Enable tests for servant-auth-client
Fixes #1474
2021-10-30 21:01:01 +02:00
Maxim Koltsov
551d4936af Fix tests for some servant-auth pkgs on GHC 9
Turns out the tests broke because of base64-bytestring issue specific to
GHC-9 that was fixed in 1.2.1.0.

Fixes #1474
2021-10-30 20:43:35 +02:00
Caroline GAUDREAU
bd9e4b1090
Merge pull request #1471 from akhesaCaro/monorepo_servant_auth
repatriation of servant-auth in the main servant repo
2021-10-29 15:03:24 +02:00
akhesacaro
e05826a799 servant-auth-swagger: Excluding building against GHC 9.0
(need base > 4.15 but swagger exclude it)
2021-10-27 18:32:46 +02:00
akhesacaro
95033be30f server-auth-server: Excluding tests against GHC 9 2021-10-27 18:32:46 +02:00
akhesacaro
7c012d70d3 servant-auth-client: Excluding tests against GHC 9 2021-10-27 18:32:46 +02:00
akhesacaro
48d22a35b8 servant-auth: removing CI status in README, Servant attribution now 2021-10-27 18:32:38 +02:00
akhesacaro
8e7a775cdd servant-auth: removing unused files from former repo 2021-10-27 18:31:20 +02:00
akhesacaro
05674e4870 change servant-auth repo url in cabal files 2021-10-26 22:31:40 +02:00
akhesacaro
119e54a800 repatriation of servant-auth in the main servant repo 2021-10-26 16:27:09 +02:00
Caroline GAUDREAU
26b01f03f2
Merge pull request #1432 from GambolingPangolin/fixes-1418
Addresses problem with URL encodings
2021-10-24 09:24:57 +02:00
Gaël Deest
abc53b54e3
Merge pull request #1462 from haskell-servant/upgrade-ghcjs
Upgrade GHCJS
2021-10-11 13:40:39 +02:00
Gaël Deest
b0f8c89472
Merge pull request #1465 from haskell-servant/eyeinsky-master
Fix documentation hierarchy
2021-10-11 13:27:34 +02:00
Markus Läll
f92d2c7ad6 Fix typo 2021-10-11 13:02:06 +02:00
Markus Läll
43760caf97 Fix documentation hierarchy 2021-10-11 13:02:06 +02:00
Théophile Choutri
9df5195710
Merge pull request #1463 from sorki/srk/foreignTypo
servant-foreign: fix haddock/example typo
2021-10-11 11:32:29 +02:00
Gaël Deest
b7c6a95929 Fix tested-with fields in Cabal files
Also re-added `servant-client` to `cabal.ghcjs.project`, setting
`buildable: False` on tests as they don't run with GHCJS.
2021-10-11 10:35:40 +02:00
Richard Marko
51c8edb74d servant-foreign: fix haddock/example typo 2021-10-11 10:29:06 +02:00
Gaël Deest
e9ae1eeed8 Remove the old Github action 2021-10-10 22:59:36 +02:00
Gaël Deest
910a3ae7ec Upgrade GHCJS to 8.6
This allows us to deprecate GHCJS 8.4 (which makes sense, as vanilla GHC
< 8.6 is already deprecated).

We re-use GHCJS from reflex-platform, which unfortunately isn't
up-to-date with latest GHC and is only 8.6. The benefit of using
reflex-platform is that it provides nix expressions for GHCJS + a binary
nix cache.

reflex-platform patches text to use a JS-String based internal
representation for performance reasons, so we provide a few haskell
dependencies from reflex-platform as well:

- hashable
- attoparsec

As those rely on text's internal representation but have been patched
for reflex-platform.
2021-10-10 22:53:26 +02:00
Ian Shipman
d5e439e56b Updates changelog 2021-10-03 09:57:55 -05:00
Ian Shipman
9666f1956b Addresses problems with URL encodings
This changes the way URL encoding for query parameters is handled,
making it possible to correctly encode arbitrary binary data into query
parameter values.

Closes #1418
2021-10-03 09:57:55 -05:00
Théophile Choutri
48bc24768e
Merge pull request #1458 from tchoutri/master
Use GHC 8.10.7 for CI & Cabal metadata
2021-10-03 12:00:59 +02:00
Théophile Choutri
c011f12d24 Use GHC 8.10.7 for CI and in Cabal metadata
Sponsored by: Scrive AB
2021-10-02 23:51:25 +02:00
Caroline GAUDREAU
5115c41617
Merge pull request #1457 from akhesaCaro/nixshell_ghc9
Update to GHC 9 (sub tasks)
2021-10-02 19:29:28 +02:00
akhesacaro
9be55b3ba3 uncomment db-sqlite-simple cookbook to add it to the building plan 2021-10-02 18:13:24 +02:00
akhesacaro
61d097db44 uncomment uverb coobook and include it from building against GHC >= 9 2021-10-02 18:09:51 +02:00
akhesaCaro
2ea6664124 GHC9 mention in Nix README. 2021-10-02 17:48:45 +02:00
Caroline GAUDREAU
0b706aa6d1
Merge pull request #1452 from akhesaCaro/unsupport_old_ghc
Unsupport GHC < 8.6.5
2021-10-02 13:41:42 +02:00
akhesacaro
a4aacc9475 A new version of hashable isn't compitable with our ghcjs. A max bound version is needed in the Cabal file 2021-10-02 13:32:15 +02:00
akhesacaro
e5f1604a9d removing Makefile deprecated with its GHC version 2021-10-02 13:13:33 +02:00
akhesacaro
e56f0092d7 remove tested-with (GHC < 8.6.5) from cabal 2021-10-02 13:13:33 +02:00
akhesaCaro
6e5dffbb91 unsupporting GHC < 8.6.5, removing unecessary imports 2021-10-02 13:13:24 +02:00
akhesaCaro
1fa1878180 unsupporting GHC < 8.6.5 in the CI 2021-10-02 13:10:20 +02:00
akhesacaro
af7d281ef0 add missing dependencies into shell.nix 2021-10-02 13:10:20 +02:00
akhesaCaro
b1a9876dc9 unsupporting GHC < 8.6.5 in the nix-shell 2021-10-02 13:08:54 +02:00
Caroline GAUDREAU
8da966f057
Merge pull request #1455 from bChiquet/Document-Raw's-behaviour
Document Raw's behaviour when composing APIs
2021-10-01 17:32:01 +02:00
bChiquet
8b93af3d12 factor in @alp's feedbacks on PR #1455 2021-10-01 16:33:16 +02:00
bChiquet
29aa10176d Document Raw's behaviour when composing APIs 2021-10-01 16:33:16 +02:00
Caroline GAUDREAU
bf160cc1ad
Merge pull request #1456 from akhesaCaro/http_common_constraint
Fix CI by adding a constraint to http-common to make it build against ghc 8.2.2
2021-10-01 15:56:53 +02:00
akhesaCaro
993277e8f4 add a coinstraint to http-common to make it build against ghc 8.2.2 2021-10-01 15:15:59 +02:00
Felix Yan
3af3129f75
Allow transformers-compat 0.7 (#1436)
Builds fine and all tests pass.
2021-08-29 15:06:09 -05:00
Brandon Chinn
799537f82d
Add serveWithContextT, ServerContext (#1441)
servant-server: add serveWithContexT and ServerContext
2021-08-21 19:15:02 +02:00
Dan Fithian
47bd25266f
Servant docs curl (#1401)
servant-dosc: generate sample curl request
2021-08-19 13:11:00 +02:00
Paolo Capriotti
19ec395e66
Avoid using SOP constructors directly (#1434)
This is a followup to #1420. It uses `respond` and `matchUnion`, with
the help of some type annotations, instead of the NS constructors from
SOP.
2021-07-13 10:10:30 -05:00
Maxim Koltsov
21682f6b72
servant-foreign 0.15.4 2021-06-23 23:47:01 +02:00
Maxim Koltsov
e2b897d3c0
Prepare 0.18.3 release (#1430) 2021-06-24 00:38:46 +03:00
Maxim Koltsov
3e29b5194e
Merge pull request #1409 from haskell-servant/maksbotan/ghc-9
Update for GHC 9.0.1
2021-06-24 00:12:26 +03:00
Maxim Koltsov
f527f09ac3
continue-on-error for doctest on GHC 9 2021-06-23 23:06:15 +02:00
Maxim Koltsov
2eba8866b7
Fix doctest running in CI 2021-06-23 23:06:15 +02:00
Maxim Koltsov
6cf2da8b64
Update GHC 8.10 to 8.10.4 in GitHub actions 2021-06-23 23:06:15 +02:00
Maxim Koltsov
4c05338876
doctest 0.18 2021-06-23 23:06:15 +02:00
Maxim Koltsov
61111178f0
Support GHC-9.0.1 2021-06-23 23:06:07 +02:00
Felix Yan
cc67b9ec6e
Allow attoparsec 0.14 (#1408)
Builds fine and all tests pass.
2021-06-21 22:54:50 -05:00
Alp
0c961f6ebb
Fix #1405 (#1429)
Request bodies are not really supposed to be used in GET requests.
2021-06-21 22:54:29 -05:00
Alp
ba30dd1700
Merge pull request #1424 from haskell-servant/update-irc-link
Update IRC link in readme to point at libera
2021-06-16 11:44:52 +02:00
Paolo Capriotti
0f9cc7eeec
Add response header support to UVerb (#1420)
* Use type wrapped in Headers h to generate response

This avoids having to define MimeRender instances for Headers.
2021-06-10 17:10:50 +02:00
Samuel Gélineau
0cb2d603c4
use Capture Description if available (#1423)
* use Capture Description if available

* update golden/comprehensive.md

This is technically a breaking change, because if a Capture has both a
Description and a ToCapture instance, the Description now takes
precedence. Since this Description wasn't doing anything before, I am
guessing that most projects currently only use Description to describe
their endpoints and not their Captures, and thus that few people will be
affected by this breaking change.

* test the "no ToCapture instance" case

The case in which there is both a Description and a ToCapture instance
seems like a corner case. The more interesting cases are the one in
which there is a Description but no ToCapture instance, and the case in
which there is a ToCapture instance but no description.
2021-06-08 13:28:19 -05:00
Maxim Koltsov
da8e64b534
Allow lens-5.0 (#1426) 2021-06-06 00:37:35 +03:00
Nathan van Doorn
26f0f93874
Update IRC link in readme to point at libera 2021-05-26 09:05:50 +01:00
Maxim Koltsov
4016aafe66
CI: add ppa:hvr/ghc in ghcjs build (#1421) 2021-05-14 16:34:16 +03:00
Maxim Koltsov
507f0a4671
Allow hspec < 2.9
https://github.com/commercialhaskell/stackage/issues/6010
2021-05-14 12:34:04 +03:00
Gaël Deest
4a79cea3ff
Merge pull request #1415 from felixonmars/patch-1
Allow singleton-bool 0.1.6
2021-04-29 15:29:02 +02:00
fisx
448c444db6
Typo (#1416) 2021-04-23 10:37:48 +02:00
Felix Yan
3c520683ce
Allow singleton-bool 0.1.6
Builds fine and all tests pass.
2021-04-21 06:16:17 +08:00
Caroline GAUDREAU
ad76c47c2f
Merge pull request #1413 from akhesaCaro/cookbook_hoist_server_with_context
enabling hoist-server-with-context cookbook
2021-04-16 13:49:57 +02:00
akhesaCaro
97967d87d1 enabling hoist-server-with-context cookbook 2021-04-16 13:39:09 +02:00
Gaël Deest
4fe6997659
Merge pull request #1412 from akhesaCaro/cookbook_https
enabling https cookbook
2021-04-10 15:47:38 +02:00
akhesaCaro
bbd016df09 enabling https cookbook 2021-04-10 15:23:57 +02:00
Caroline GAUDREAU
486f89da04
Merge pull request #1410 from akhesaCaro/improve_nix_shell
pinning nixpkgs and adding missing dependencies to shell.nix file
2021-04-10 11:59:17 +02:00
akhesaCaro
4a7a1080a0 pinning nixpkgs and adding missing dependencies 2021-04-10 11:30:37 +02:00
Caroline GAUDREAU
bc6144716b
Merge pull request #1407 from akhesaCaro/using_derivingvia_uverb
Using DerivingVia in UVerb's cookbook
2021-04-09 16:43:12 +02:00
Gaël Deest
f30b72cc90 Add DeriveAnyClass to UVerb.lhs (not implied by DerivingVia on 8.6.5) 2021-04-09 16:19:51 +02:00
Gaël Deest
81a73dfcda Try excluding uverb cookbook from pre 8.6.1 builds 2021-04-09 15:59:02 +02:00
Caroline GAUDREAU
d06b65c4e6
Merge pull request #1390 from Profpatsch/document-servant-foreign
Document servant-foreign
2021-03-25 12:21:20 +01:00
Philip Patsch
e4865644c1 doc(servant-foreign): Document module
I spend some considerable time reverse engineering the module, so I
thought I’d write the documentation I would have liked to see.

The strategy here is that a user not necessarily has insight into how
servant works internally, or even how to write complex servant routes,
they just want to generate a list of endpoints and convert the `Req`
type into e.g. generated code in $language. Thus, they need to know
the semantics of all fields of Req, how they interact and how they
relate to a plain http route.

I made sure every `f` is replaced with `ftype`, so we have one
conventional way of referring to the foreign type argument everywhere.

Some enums are not set at all, they are marked as such.

`_reqBodyContentType` introduces a major restriction of the module, so
that is mentioned in the documentation for now, until the time it will
be fixed.

A few TODO’s describe places where types don’t make sense but would
introduce API-breaking changes, so these should probably be
simplified,
but bundled in one go.
2021-03-25 11:26:53 +01:00
Philip Patsch
07f7954cc6 chore(servant-foreign): remove dead type Frag
It is not used anywhere and also not exported.
2021-03-25 11:26:53 +01:00
Philip Patsch
a0265097e8 doc(servant-foreign): reorder imports
The imports were ordered in the worst possible way, with all
undocumented small type definitions coming first and the actual meat
of the module coming at the very end, mixed in with irrelevant
functions.

This inverses that toxic ordering, showing the main function
first (`listFromAPI`) and then the main data type (`Req`) and the main
class (`HasForeignType`).
2021-03-25 11:26:53 +01:00
Philip Patsch
c3a517cb4f doc(servant-foreign): Inflection docs & module docs 2021-03-25 11:26:53 +01:00
akhesaCaro
d4f7b0397d adding a compatibility warning in the cookbook 2021-03-23 14:10:14 +01:00
akhesaCaro
ba379287c8 reverting : removing DerivingVia extension (not compatible ghc < 8.6.1) 2021-03-23 14:09:49 +01:00
Gaël Deest
0743ca724d
Merge pull request #1403 from haskell-servant/ci-fix-followups
Try and improve caching on CI
2021-03-18 12:17:58 +01:00
Gaël Deest
a28856a11a Rename GH action file to match branch name 2021-03-18 12:10:10 +01:00
Gaël Deest
d6fb3826c8 Try and improve caching 2021-03-18 11:48:50 +01:00
Gaël Deest
53e943b5bb
Merge pull request #1397 from akhesaCaro/compiling_sqlite_simple_cookbook
Fixing Servant cookbooks part 1 (-testing + sqlite-simple)
2021-03-18 11:09:21 +01:00
akhesaCaro
269e546a6a sqlite-simple cookbook is working with sqlite-simple >= 0.4.5.0 2021-03-18 10:50:17 +01:00
akhesaCaro
dd1ab6dd36 fixing DocSpec tests to make them compile 2021-03-18 07:43:09 +01:00
Gaël Deest
a74d9d911e
Merge pull request #1402 from haskell-servant/fix-ci
Basic GitHub actions-based CI
2021-03-17 23:50:13 +01:00
Gaël Deest
507990cafe Switch to actively maintained haskell/actions/setup for CI 2021-03-17 23:22:20 +01:00
Gaël Deest
6452942a69 Cleanup 2021-03-17 23:22:20 +01:00
Gaël Deest
95d4f5030f Build / tests with GHCJS 2021-03-17 23:22:20 +01:00
Gaël Deest
579a372eb9 Enable all tested-with GHC versions 2021-03-17 23:22:20 +01:00
Gaël Deest
f9dd1f691f Try installing GHCJS via HVR's PPA 2021-03-17 23:22:20 +01:00
akhesacaro
9357583459 removing DerivingStrategies extension (not compatible ghc < 8.2.1) 2021-03-17 23:22:20 +01:00
akhesacaro
08b5e86536 (<>) needs the import of Data.Semigroup (ghc 8.2.2) 2021-03-17 23:22:20 +01:00
akhesacaro
86eb25018e removing DerivingVia extension (not compatible ghc < 8.6.1) 2021-03-17 23:22:20 +01:00
Gaël Deest
133ed94442 Re-enable doctests on CI 2021-03-17 23:22:20 +01:00
Gaël Deest
613dcf9ed5 Basic GitHub actions-based CI
- Setup a basic CI based on GitHub actions, with a somewhat limited build matrix.
- Disable cookbook/testing, because servant-quickcheck doesn't build anymore.
- Disable servant-docs on Cabal build, because of some test failures
  - The order of some JSON fields seems to be reversed in the output, need investigation.
- Fix test failures in servant-http-streams when `localhost` points to an IPv6 address rather than 127.0.0.1.
2021-03-17 23:22:20 +01:00
Gaël Deest
f1b5a64466 Remove Travis / haskell-ci configuration
Also remove mention about re-generating .travis.yml in README
2021-03-17 23:22:20 +01:00
Bodigrim
27173c9223
Allow bytestring-0.11 (#1386) 2020-12-16 11:04:49 +01:00
Ondřej Súkup
1f701aa97d
Update upper bound limit for http-client (#1384) 2020-12-11 22:52:32 +01:00
Ondřej Súkup
7412ac3472
Update upper bound limit of base64-bytestring (#1383) 2020-12-11 21:15:27 +01:00
Philipp Balzarek
7675e725d2
Bump base64-bytestring limit to 1.3 (#1382) 2020-12-11 00:32:16 +01:00
fisx
6ebb9e419e
Fix overlapping MimeRender instances (#1376) 2020-12-09 23:08:54 +01:00
fisx
505e6d346b
Merge pull request #1377 from haskell-servant/housekeeping
Housekeeping
2020-12-09 23:07:47 +01:00
Matthias Fischmann
fe849b27bf
bump stack.yaml resolver. 2020-12-06 16:04:02 +01:00
Matthias Fischmann
2f20c32704
Don't warn about necessary, expected type errors. 2020-12-06 16:03:19 +01:00
Intolerable
a8f584f80b
add HasLink instance for UVerb (#1370) 2020-12-06 14:19:35 +01:00
Alexey Kuleshevich
08579ca003
Update upper bounds for QuickCheck (#1375) 2020-12-05 20:49:11 +01:00
Domen Kožar
0bda65e315
links: import toUrlPiece to make it clear where it comes from 2020-12-05 17:00:03 +01:00
Arian van Putten
f7dc40ca8d
servant-client-core: depend on 0.18.2 (#1366) 2020-11-25 15:06:45 +03:00
Maxim Koltsov
57badc7c74
Add UVerb cookbook to cookbook build (#1365) 2020-11-22 18:08:01 +03:00
Maxim Koltsov
0ad2bd221a
Prepare 0.18.2 release (#1364) 2020-11-22 17:51:32 +03:00
Andrey Prokopenko
ce638027a8
Remove extra parameter from haddock section of Fragment instances (#1362) 2020-11-22 11:08:32 +01:00
Felix Yan
aa4f54e92e
Correct a typo in UVerb.hs (#1363) 2020-11-22 11:08:11 +01:00
Felix Yan
1d0b34df50
Allow QuickCheck 2.14 (#1359)
Builds fine and all tests pass.
2020-11-22 11:07:54 +01:00
Andrey Prokopenko
da0c83d318
Add URI fragment as a separate combinator (#1324) 2020-11-18 21:57:20 +03:00
Arian van Putten
339eec6a90
Fix overlapping instance for WithStatus (#1361)
We do not need the `ToJSON` instance for `WithStatus`
it would cause an overlap between:

```
ToJSON a => MimeRender JSON a
```

and

```
forall cty a.  MimeRendercty  a =>  MimeRender cty (WithStatus a)
```
and Servant just needs the `MimeRender` typeclass for it to work

* Add some more docs to the UVerb module

* cookbook/uverb: Change GHC versions

CI was complaining some version did not exist. Trying to bump
Also added 8.10.1

* doc/cookbook/uverb: Remove 8.4.4 from tested versions

CI was running into a cabal bug for some reason
2020-11-18 17:33:03 +01:00
Никита Размахнин
0ea692bb64 Add support of Pretty modifier for all verbs aliases
Minor import warning fix
2020-11-11 10:22:55 +03:00
Maxim Koltsov
8e2a654e0e
Merge pull request #1355 from haskell-servant/fix-constraints
Update inter-library version constraints
2020-11-05 11:16:14 +03:00
Maxim Koltsov
4c72c08830
Update inter-library version constraints 2020-11-05 10:48:38 +03:00
Maxim Koltsov
c95faa53fe
Merge pull request #1354 from haskell-servant/bump-versions
Bump versions
2020-11-04 22:28:15 +03:00
Maxim Koltsov
bd698cad3b
Bump version in preparation for new release 2020-11-04 17:06:51 +03:00
Maxim Koltsov
9e4a97eb78
Loosen upper bound on wai-extra 2020-11-04 15:11:15 +03:00
fisx
c1105899f4
union verbs (#1314) 2020-10-31 20:45:46 +01:00
fisx
64f3543034
bump "tested-with" ghc versions. (#1350) 2020-10-25 14:24:06 +01:00
Domen Kožar
81ce30302c
Merge pull request #1351 from haskell-servant/arianvp-patch-1
Update FUNDING.yml
2020-10-20 11:56:43 +02:00
Arian van Putten
6a66ca6d65
Update FUNDING.yml
I have a Github Sponsors account now. so people can sponsor me directly on Github.
2020-10-14 10:43:57 +02:00
Alejandro Serrano
0c0fe5b9d3
Loosen bound on base16-bytestring (#1348) 2020-10-12 13:28:35 +02:00
Cary Robbins
83bbc6d520
Add instance for ToSample NonEmpty (#1330) 2020-10-01 14:02:43 +02:00
Felix Yan
264846a61f
Allow hspec-wai 0.11 (#1343)
Builds fine and all tests pass.
2020-10-01 10:16:27 +02:00
Brandon Chinn
e364470dd9
Fix docs: emptyAPIServer -> emptyServer (#1344) 2020-10-01 09:58:50 +02:00
Szabo Gergely
e3a29addf4
Fix servant-docs code sample in README (#1335) 2020-09-03 06:43:10 +02:00
Matthias Fischmann
1760cc8527
Bump more package versions. 2020-09-01 14:21:26 +02:00
Matthias Fischmann
27f9662830
Remove x-revision in servant-conduit. (Oops.) 2020-09-01 12:24:38 +02:00
Matthias Fischmann
2906f0137c
Bump minor version of servant-conduit. 2020-09-01 12:21:57 +02:00
fisx
0d97d76c3b
Merge pull request #1332 from felixonmars/http-api-data-0.4.2
Allow http-api-data 0.4.2
2020-08-31 12:10:37 +02:00
Felix Yan
b4b649c8f4
Allow http-api-data 0.4.2
Builds fine and all tests pass here.
2020-08-30 15:26:48 +08:00
fisx
1e4872c8b6
Merge pull request #1328 from jkaye2012/patch-1
Minor rewording in Server tutorial
2020-08-02 14:57:42 +02:00
Jordan Kaye
9f8127ed54
Minor rewording in Server tutorial
Fixed an awkward wording in the Server tutorial.
2020-08-02 05:51:08 -06:00
Matthias Fischmann
858fb6cce5
Fix: remove x-revision from servant-server.cabal. 2020-07-31 20:25:31 +02:00
Matthias Fischmann
6dcb29bada
Update changelogs. 2020-07-31 20:19:07 +02:00
fisx
be679589bd
Merge pull request #1327 from maksbotan/maksbotan/bump-version
Reenable cookbook-testing
2020-07-31 13:24:40 +02:00
Maxim Koltsov
e93376939c
Reenable cookbook-testing 2020-07-31 13:32:17 +03:00
fisx
067ab350ef
Merge pull request #1326 from maksbotan/maksbotan/bump-version
Bump version to 0.18
2020-07-31 09:24:09 +02:00
Maxim Koltsov
d740c18992
Explicit export list in ErrorFormatter.hs 2020-07-30 19:05:46 +03:00
Maxim Koltsov
43cf589e0e
Bump version to 0.18 2020-07-30 19:03:58 +03:00
fisx
c5717a61a3
Merge pull request #1312 from maksbotan/maksbotan/configurable-combinator-errors
Configurable combinator errors
2020-07-30 17:15:59 +02:00
fisx
4a6db6e5ff
Merge pull request #1321 from andys8/patch-1
Docs: Hoist Server "Footnote"
2020-07-21 09:28:53 +02:00
Andy
55f5a78b1b
Docs: Hoist Server "Footnote" 2020-07-21 01:02:05 +02:00
Maxim Koltsov
cb0224d063
Add 8.10.1 to tested-with, haskell-ci regenerate 2020-07-17 17:17:45 +03:00
Maxim Koltsov
d94ad9df9b
Add cookbook entry for custom error formatters 2020-07-17 17:11:46 +03:00
Maxim Koltsov
bd2a813c1a
TEMP disable cookbook/testing 2020-07-17 17:11:46 +03:00
Maxim Koltsov
7218c66fd0
haskell-ci regenerate 2020-07-17 17:11:45 +03:00
Maxim Koltsov
1a09b1d3a4
Update GHC 8.8.x versions to 8.8.3 2020-07-17 17:10:31 +03:00
Maxim Koltsov
cb80fa6263
Add tests for custom error formatters 2020-07-17 17:10:31 +03:00
Maxim Koltsov
57f0b0b390
Make error messages from combinators configurable
Currently there is no way for Servant users to customize formatting of
error messages that arise when combinators can't parse URL or request
body, apart from reimplementing those combinators for themselves or
using middlewares.

This commit adds a possibility to specify custom error formatters
through Context.

Fixes #685
2020-07-17 17:10:31 +03:00
fisx
1f1f7f309a
Merge pull request #1318 from haskell-servant/ghc_810
Ghc 810
2020-07-03 08:12:40 +02:00
Matthias Fischmann
eaadc9ec1f
Relax upper bound for aeson. 2020-07-03 06:57:52 +02:00
Leif Warner
e3c4f5d85e
Bump doctest version used for ghc 8.10.1 2020-07-03 06:57:52 +02:00
Leif Warner
7ddc2e7b9e
Add GHC 8.10.1 to .travis.yml build. 2020-07-03 06:57:52 +02:00
Leif Warner
0530671ad6
Allow newer versions of base, template-haskell, lens, & unliftio-core 2020-07-03 06:57:52 +02:00
fisx
7f4ae61a01
Merge pull request #1310 from Taneb/knownstatus
Add KnownStatus typeclass
2020-06-13 17:02:41 +02:00
Nathan van Doorn
ff9da1cde4 Use GHC.TypeLits rather than TypeNats 2020-06-13 15:50:12 +01:00
Nathan van Doorn
6889d053c7 Add FlexibleInstances for earlier GHCs 2020-06-13 15:38:36 +01:00
Nathan van Doorn
a8184a2ee0 Add KnownStatus typeclass 2020-06-13 15:10:07 +01:00
Felix Yan
4b225c23d7
Allow aeson 1.5 in all components (#1309) 2020-06-12 15:02:39 -04:00
Felix Yan
c778a18372
Allow aeson 1.5 (#1302)
Builds fine and all tests pass here.
2020-06-12 02:38:09 -04:00
Jan Hrcek
8e7b538921
More cookbook improvements (#1305)
* Simplify code in CurlMock cookbook recipe

* Link to latest versions of packages on hackage

* Fix grammar in the OpenIdConnect recipe

* HasForeignType -> HasForeign
2020-06-10 12:36:23 -04:00
Jan Hrcek
b9d8fbcdc1
Fix typos and grammar (#1304)
* Fix typos and grammar

* Remove redundant words, fix articles

* More language fixes

* More typo fixes and resolve TODO about missing links
2020-06-06 00:43:51 -04:00
Teymour Aldridge
67cb564aef
Update README.md (#1300)
Grammatical fix to documentation
2020-06-06 00:30:16 -04:00
David Johnson
85599b944c
Build servant repo with nix. (#1288) 2020-06-06 00:04:32 -04:00
Domen Kožar
e7bdd097bd
Merge pull request #1287 from tfausak/patch-2
Allow lens 4.19
2020-04-29 21:39:42 +02:00
Domen Kožar
e05ac51c8f
Merge pull request #1286 from felixonmars/patch-2
Allow QuickCheck 2.14
2020-04-29 21:39:26 +02:00
Domen Kožar
40ac6fb080
Merge pull request #1293 from felixonmars/patch-3
Allow base64-bytestring 1.1
2020-04-29 21:39:01 +02:00
Felix Yan
a221320f5a
Allow base64-bytestring 1.1
Builds fine and all tests pass here.
2020-04-26 11:35:01 +08:00
Taylor Fausak
6bd3ee80ca
Allow lens 4.19
https://github.com/ekmett/lens/blob/v4.19.1/CHANGELOG.markdown
2020-04-05 12:15:30 -04:00
Felix Yan
8f60a02c25
Allow QuickCheck 2.14
Builds fine and all tests pass.
2020-04-02 06:58:22 +08:00
Oleg Grenrus
567eb733d2
Merge pull request #1279 from theophile-fl/docs/improve-genericServerT
docs(generic): Improve the documentation for `genericServerT`
2020-03-04 18:23:38 +02:00
Théophile Choutri
b3b3dc9f41 docs(generic): Improve the documentation for genericServerT
This commit adds an explanation and a link to the Servant Cookbook
to `genericServerT`.

Moreover, the `genericServer` and `genericServe`'s haddocks are
slightly edited to add a missing 'a'.
2020-03-04 15:53:37 +01:00
Oleg Grenrus
5998429e81
Merge pull request #1269 from haskell-servant/drop-allow-newer
Drop most allow-newer in cabal.project
2020-01-24 13:17:15 +02:00
Oleg Grenrus
7916051114 Drop most allow-newer in cabal.project
Dependencies are updated to allow servant-0.17
2020-01-24 12:54:40 +02:00
Oleg Grenrus
c15f550e1f Incorrect lower bound in servant-server 2020-01-23 22:26:02 +02:00
Oleg Grenrus
b30286312f Fix typo in servant-docs changelog 2020-01-23 14:10:20 +02:00
Oleg Grenrus
74aa1d52ec
Merge pull request #1268 from haskell-servant/version-0.17
Bump version to 0.17
2020-01-23 13:58:03 +02:00
Oleg Grenrus
b318e69bff Flush changelog-d 2020-01-23 13:43:46 +02:00
Oleg Grenrus
b519014f96 Update other changelogs 2020-01-23 13:43:19 +02:00
Oleg Grenrus
8fc47edf99 Remove deprecated modules (end of 2019 is passed) 2020-01-23 13:14:24 +02:00
Oleg Grenrus
3bf4b100a8 Bump version to 0.17 2020-01-23 12:50:07 +02:00
Oleg Grenrus
837243631d
Merge pull request #1266 from haskell-servant/ghc-8.8.2
Use GHC-8.8.2 on Travis
2020-01-21 18:20:26 +02:00
Oleg Grenrus
d29b0cc8f1 Use GHC-8.8.2 on Travis 2020-01-21 16:52:20 +02:00
Oleg Grenrus
cc1e921824
Merge pull request #1264 from haskell-servant/remove-jsaddle
Remove servant-jssadle (moved to own repository)
2020-01-10 02:25:39 +02:00
Oleg Grenrus
21e6000b09 Remove servant-jssadle (moved to own repository) 2020-01-10 01:20:48 +02:00
Oleg Grenrus
831f46ab78
Merge pull request #1263 from haskell-servant/build-type-simple
Change build-type: Simple; run doctests on CI via haskell-ci
2020-01-10 01:18:39 +02:00
Oleg Grenrus
524b07224f Change build-type: Simple; run doctests on CI via haskell-ci
Don't use hspec-discover in tutorial,
so doctests work on CI
2020-01-10 01:07:31 +02:00
Oleg Grenrus
ec0cd8a947
Merge pull request #1262 from haskell-servant/rawQueryString
Refactor of #1249
2020-01-09 20:12:45 +02:00
Ilia Rodionov
40582c40e4 add query rewriting tests and changelog item
add prs: #1249

add ps1249 changelog item
2020-01-09 15:08:24 +02:00
Ilia Rodionov
28c4533659 use queryString not rawQueryString, enables param rewrites with Middleware possible 2020-01-09 14:43:14 +02:00
Oleg Grenrus
b4e5aa0def Add 1247 to documentation updates changelog 2019-12-19 13:16:04 +02:00
Alp Mestanogullari
00d9b60796
Merge pull request #1254 from natsuki14/patch-1
I guess some sample curl commands are wrong.
2019-12-19 07:53:54 +01:00
Oleg Grenrus
9536f1d853
Merge pull request #1258 from haskell-servant/pr-1247
PR 1247
2019-12-15 18:23:23 +02:00
Matthias Heinzel
069d087874 Fix compilation warnings 2019-12-15 17:04:06 +02:00
Oleg Grenrus
544487d15a Update stack.yaml to resolver lts-14.17 2019-12-15 16:05:09 +02:00
Oleg Grenrus
4ece88e4d9
Merge pull request #1257 from haskell-servant/warp-3.3b
Allow warp-3.3 in master
2019-12-15 12:47:23 +02:00
Oleg Grenrus
f7d5c0149f Allow warp-3.3 in master 2019-12-15 01:57:29 +02:00
Oleg Grenrus
0215b60403
Merge pull request #1256 from haskell-servant/update-changelog-sofar
Go through changelog entries so far
2019-12-15 01:07:39 +02:00
Oleg Grenrus
05b64ed652 Go through changelog entries so far 2019-12-14 23:16:24 +02:00
Oleg Grenrus
65c6298e89
Merge pull request #1255 from haskell-servant/pr-1213
added a function to create Client.Request in ClientEnv
2019-12-14 23:13:20 +02:00
Eric Torreborre
164ae93c31 added a function to create Client.Request in ClientEnv 2019-12-14 22:34:06 +02:00
Oleg Grenrus
e229efdf25
Merge pull request #1241 from Simspace/issue-1240
merge documentation from duplicate routes
2019-12-14 22:23:25 +02:00
Oleg Grenrus
e1039523ec
Merge pull request #1238 from roberth/redact-authorization-header
servant-client-core: Redact Authorization header
2019-12-14 22:22:47 +02:00
Oleg Grenrus
78cf24af40 Add changelog.d config 2019-12-14 22:20:51 +02:00
natsuki14
2050ec86a2
I guess some sample curl commans are wrong. 2019-12-15 03:09:01 +09:00
Oleg Grenrus
d9e653cdc1
Merge pull request #1250 from haskell-servant/travis-update-2019-12-09
Try to fix CI: remove servant-jssaddle, install npm
2019-12-09 23:12:30 +02:00
Oleg Grenrus
ce5939b837 Try to fix CI: remove servant-jssaddle, regenerate .travis.yml 2019-12-09 17:53:36 +02:00
Oleg Grenrus
971d14032a
Merge pull request #1245 from haskell-servant/small-updates
Small updates
2019-11-18 00:57:42 +02:00
Oleg Grenrus
83336ae991 Small updates
- Allow newer js-jquery in tutorial
- use bionic on Travis
- allow newer hspec in servant-jsaddle
2019-11-17 19:39:44 +02:00
Oleg Grenrus
cc7dc408d7
Merge pull request #1244 from jhrcek/docFixes
Various haddock fixes
2019-11-15 09:18:20 +02:00
Robert Hensing
13b21cbbf1 Add changelog entry 2019-11-12 15:01:35 +01:00
Jan Hrček
da365b1e47 Various haddock fixes 2019-11-12 09:29:35 +01:00
Oleg Grenrus
1c4e3c4a93
Merge pull request #1242 from haskell-servant/haskell-ci-update
Regenerate .travis.yml
2019-11-11 16:01:03 +02:00
Oleg Grenrus
8507cc509b Regenerate .travis.yml 2019-11-11 10:11:34 +02:00
Samuel Gélineau
0cfd9e6597 test "merge documentation from duplicate routes" 2019-11-07 19:31:29 -05:00
Samuel Gélineau
fdb1e030e6 add changelog.d entry 2019-11-07 19:08:45 -05:00
Samuel Gélineau
1f6d7d7ea8 remove leftover debug code 2019-11-07 19:01:56 -05:00
Samuel Gélineau
143091eb3f merge documentation from duplicate routes
Servant supports defining the same route multiple times with different
content-types and result-types, but servant-docs was only documenting
the first of copy of such duplicated routes. It now combines the
documentation from all the copies.

Unfortunately, it is not yet possible for the documentation to specify
multiple status codes.
2019-11-07 18:53:41 -05:00
Robert Hensing
ce3c68f94b servant-client-core: Redact Authorization header 2019-11-05 16:31:06 +01:00
Oleg Grenrus
925d50d752
Merge pull request #1234 from peeley/fix_broken_links_1206
Fixes issue #1206 by updating broken links in tutorial.
2019-10-08 06:23:29 +03:00
Noah Snelson
5612772448 Change Type Families user manual link to specify GHC 8.8.1 2019-10-07 18:55:47 -07:00
Noah Snelson
ec80f251f3 Fixes issue #1206 by updating broken links in tutorial. 2019-10-07 11:23:30 -07:00
Oleg Grenrus
6cf7c73824
Merge pull request #1233 from haskell-servant/servant-jsaddle-test-delays
Add few delays in servant-jsaddle tests
2019-10-01 17:28:17 +03:00
Oleg Grenrus
99aa09e65b Catch WS.ConnectionClosed 2019-10-01 16:28:28 +03:00
Oleg Grenrus
6d0c415377 Add few delays in servant-jsaddle tests
Hopefully they will fail less on Travis with these
2019-10-01 13:50:26 +03:00
Oleg Grenrus
1047510141
Merge pull request #1230 from haskell-servant/changelog-d
Add changelog.d directory, amend CONTRIBUTING.md
2019-09-30 21:53:00 +03:00
Oleg Grenrus
ed201224dc Add changelog entry 2019-09-30 21:17:13 +03:00
Oleg Grenrus
0cc1109c05 Add changelog.d directory, amend CONTRIBUTING.md 2019-09-30 19:40:18 +03:00
Oleg Grenrus
d3aba7aac7
Merge pull request #1229 from haskell-servant/servant-jsaddle-8.8
Build jsaddle with GHC-8.8
2019-09-30 15:59:44 +03:00
Oleg Grenrus
71ca2a203c Allow jsaddle-dom-0.9.3.1 2019-09-30 10:53:32 +03:00
Oleg Grenrus
52408fea16 Fix servant-client with base-compat-0.11 2019-09-30 10:34:33 +03:00
Oleg Grenrus
fa35b5bd70 Build jsaddle with GHC-8.8
Closes https://github.com/haskell-servant/servant/issues/1227
2019-09-29 23:55:10 +03:00
Oleg Grenrus
38f3da2499 Add TFB readme section 2019-09-29 23:27:47 +03:00
Oleg Grenrus
002ee3cd8f
Merge pull request #1228 from haskell-servant/pull-1219-no-content-verb-1028
Pull 1219: no content verb 1028
2019-09-29 21:22:23 +03:00
Catherine Galkina
b4372b5c14 Removed unnecessary OVERLAPPING/OVERLAPPABLE pragmas 2019-09-29 14:18:13 +03:00
Catherine Galkina
8550926d90 Updated docs 2019-09-29 14:18:13 +03:00
Catherine Galkina
dcf307d67a Fixed tests for servant-http-streams 2019-09-29 14:18:13 +03:00
Catherine Galkina
0cbed24f23 Added HasClient and HasForeign instances for NoContentVerb 2019-09-29 14:18:13 +03:00
Catherine Galkina
0ec5af11f5 Fixed docs for NoContent endpoints 2019-09-29 14:18:13 +03:00
Catherine Galkina
b440af900b Implemented NoContentVerb and server instances for it 2019-09-29 14:18:13 +03:00
Oleg Grenrus
164f75711b
Merge pull request #1224 from haskell-servant/forward-8.8
Forward 8.8
2019-09-29 14:15:58 +03:00
Oleg Grenrus
f089f8d0b2 Relax bounds for ghc-8.8 2019-09-29 00:43:53 +03:00
Oleg Grenrus
19dee18f71 Remove control-monad-omega dependency 2019-09-28 17:43:28 +03:00
Oleg Grenrus
56773a7649 Comment out cookbooks unbuildable with ghc-8.8 2019-09-28 17:43:28 +03:00
Oleg Grenrus
1dbd258510 Re-organise cabal.project (for GHC-8.8) 2019-09-28 17:43:28 +03:00
Oleg Grenrus
7f10f7fbc6 Bump servant-pipes version 2019-09-12 09:45:00 +03:00
Oleg Grenrus
09f452ba07 We use fail in servant-pipes 2019-09-12 09:44:54 +03:00
Oleg Grenrus
b04261bf4d Remove screenshot.png 2019-09-08 12:09:33 +03:00
Oleg Grenrus
6e3af85c93
Merge pull request #1216 from haskell-servant/servant-client-jsaddle
Add servant-jsaddle
2019-09-08 10:59:01 +02:00
Robert Hensing
e14a14fb5c Add servant-client-jsaddle
- servant-client-jsaddle: Remove some debug printing
- Update .travis.yml with haskell-ci
- servant-client-jsaddle: bump base bounds
- Add libgirepository1.0-dev
- servant-client-jsaddle: bump upper bound on containers
- servant-client-jsaddle: relax upper bound on semigroupoids
- servant-client-jsaddle: bump servant-client-core dependency
- servant-client-jsaddle: fix compatibility
- servant-client-jsaddle: import correct module
- .travis.yml: run xvfb for headless GUI testing
- Use ghcjs-dom instead of jsaddle-dom directly.
- Also use ghcjs-dom in tests.
- Ignore exceptions on send - they are handled in toResponse.
- Apparently ghcjs-dom does use the same exception these days.
- Got rid of obsolete comment.
- Make sure response gets handled even in case of exception.
- Update servant-client-jsaddle/servant-client-jsaddle.cabal

Dependingon ghcjs-dom avoids the dependency on jsaddle-dom on ghcjs and
have slightly better performance on ghcjs.

Co-Authored-By: Herbert Valerio Riedel <hvr@gnu.org>
2019-09-08 10:28:21 +03:00
Oleg Grenrus
12e64fd3d2
Merge pull request #1214 from haskell-servant/ghcjs-travis
Regenerate .travis.yml; GHC-8.6.5; add GHCJS
2019-09-07 14:19:14 +03:00
Oleg Grenrus
ecbc04bbee Regenerate .travis.yml; GHC-8.6.5; add GHCJS 2019-09-07 14:03:46 +03:00
Alp Mestanogullari
7de93f9a51
Merge pull request #1204 from przembot/fix/issue-1200
Fix Verb with headers checking content type differently (and add test for it)
2019-08-16 20:41:40 +02:00
Alp Mestanogullari
35cae91fdb
Merge pull request #1194 from rl-king/doc-test-warpserver
Testing cookbook recipe: use Warp.testWithApplication to prevent race condition
2019-08-16 20:39:56 +02:00
David Johnson
3712b200e2
Remove more unused extensions (#1203)
* Remove additional unused extensions.

* Add missing extensions
2019-08-15 03:08:12 -04:00
Przemysław Kopański
c780e349a0 Fix Verb with headers checking content type differently 2019-08-11 21:19:34 +02:00
David Johnson
680d218719
Merge pull request #1201 from haskell-servant/remove-unused-extensions
Remove unused extensions from servant cabal file.
2019-08-09 15:56:04 -04:00
David Johnson
aca1fb216c Remove unused extensions from servant cabal file.
Useful for cross-compilation.
2019-08-09 00:33:19 -04:00
Alp Mestanogullari
e9aec26fb2
Merge pull request #1198 from jakequade/cookbook-mysql-basic
Add mysql cookbook example
2019-08-07 10:41:27 +02:00
Jake Quade
7554ed4ae3 Add mysql cookbook example
Contributes a basic mysql-backed API with basic CRUD functionalities.
2019-08-05 15:32:57 +10:00
Oleg Grenrus
da6ea7b58f Add changelog for 0.16.2 2019-08-03 17:20:39 +03:00
Oleg Grenrus
4edd164650
Merge pull request #1197 from sopvop/headers-stream
HasClient instance for Stream with Headers
2019-07-29 09:37:22 +03:00
Leonid Onokhov
dbd92a4885 HasClient instance for Stream with Headers
Fixes #1170
2019-07-26 10:30:06 +00:00
rl-king
da174d9887 Use Warp.testWithApplication to prevent race conditions 2019-07-16 22:16:59 +02:00
jake
d4289931ad add stack to tutorial docs (#1177)
* add stack to tutorial docs

* adjusted stack install wording
2019-07-10 09:14:39 +02:00
Alp Mestanogullari
db9b258fdc
Merge pull request #1190 from haskell-servant/domenkozar-patch-1
Add sponsorship button
2019-07-03 21:55:42 +02:00
Domen Kožar
e1e8e3f75c
Add sponsorship button 2019-07-02 11:42:24 +02:00
Oleg Grenrus
0f590dc7d9
Merge pull request #1188 from felixonmars/patch-1
Allow singleton-bool 0.1.5
2019-06-24 21:12:26 +03:00
Felix Yan
5557920de3
Allow singleton-bool 0.1.5
Builds fine and all tests pass here.
2019-06-25 00:02:57 +08:00
Oleg Grenrus
4bd2aa10cd
Merge pull request #1183 from haskell-servant/allow-newer-stuff
Allow newer network, semigroups, hashable, machines
2019-05-28 16:03:26 +03:00
Oleg Grenrus
612038585a Allow newer network, semigroups, hashable, machines 2019-05-28 15:27:52 +03:00
Oleg Grenrus
fffb513d06
Merge pull request #1181 from felixonmars/http-api-data-0.4.1
Allow http-api-data 0.4.1
2019-05-28 15:16:46 +03:00
Felix Yan
91520e20ef Allow http-api-data 0.4.1
Builds fine and all tests pass here.
2019-05-28 17:44:08 +08:00
Oleg Grenrus
1d9b0a51ef
Merge pull request #1182 from haskell-servant/fix-travis
Fix travis: remove open-id-connect cookbook, it's bitrotted
2019-05-28 12:40:45 +03:00
Oleg Grenrus
cd42676a2c Fix travis: remove open-id-connect cookbook, it's bitrotted 2019-05-28 11:57:36 +03:00
Oleg Grenrus
48df41065f
Merge pull request #1175 from turion/patch-1
Fix small typos in doc/tutorial/Server.lhs
2019-04-20 11:57:32 +03:00
Manuel Bärenz
0ccb02dad1
Fix small typos in doc/tutorial/Server.lhs 2019-04-20 10:52:49 +02:00
Alp Mestanogullari
77d7b3fca3
Merge pull request #1174 from domenkozar/docs-streaming
Add streaming to the cookbook toc
2019-04-19 12:36:30 +02:00
Domen Kožar
1826b8a767
Add streaming to the cookbook toc 2019-04-19 14:45:14 +07:00
Oleg Grenrus
d4788fb508
Merge pull request #1173 from haskell-servant/http-media-0.8-master
Http media 0.8 master
2019-04-16 14:29:22 +03:00
Oleg Grenrus
6d90d48b36 http-media-0.8 changed mapAcceptMedia 2019-04-16 13:58:04 +03:00
Oleg Grenrus
b534a8c2cf Implement forgotten mappend 2019-04-16 13:09:23 +03:00
Oleg Grenrus
ec5574b3b7 Allow http-media-0.8 and QuickCheck-2.13 2019-04-16 13:07:28 +03:00
Oleg Grenrus
94e00d3c74
Merge pull request #1171 from haskell-servant/issue-1088-squash
Adding OIDC Cookbook
2019-04-15 21:09:02 +03:00
Oleg Grenrus
3c9af80cb9
Merge pull request #1154 from mhcurylo/ClientSpec_split
ClientSpec split into multiple modules
2019-04-15 20:07:39 +03:00
Yann Esposito (Yogsototh)
53b97dd8b3 Adding OIDC Cookbook 2019-04-15 19:50:28 +03:00
Oleg Grenrus
726380964a
Merge pull request #1162 from ethercrow/patch-1
Fix the example in Servant.Server
2019-04-03 02:21:06 +03:00
Dmitry Ivanov
54812a9079
Fix the example in Servant.Server 2019-04-02 17:58:48 +02:00
Mateusz Curylo
a91cde109f ClientSpec split into multiple modules 2019-03-31 13:21:17 +01:00
Oleg Grenrus
cfe4869303
Merge pull request #1157 from haskell-servant/travis-test
Try refactored haskell-ci
2019-03-27 14:14:28 +02:00
Oleg Grenrus
1b40ff297f Try refactored haskell-ci 2019-03-27 11:22:31 +02:00
Oleg Grenrus
73e00a431d
Merge pull request #1159 from haskell-servant/pull-1158
Added Semigroup and Monoid instances for SourceT
2019-03-27 01:42:34 +02:00
Oleg Grenrus
dc4125dcc4
Merge pull request #1156 from wireapp/feature/capture-lenient
Feature/capture lenient
2019-03-27 01:15:49 +02:00
Science!
5c86e11a21 added Semigroup and Monoid instances for SourceT 2019-03-27 01:04:14 +02:00
jschaul
6cbf0d3891
add route to comprehensive API 2019-03-20 13:03:16 +01:00
jschaul
a4e5707955
add test 2019-03-18 17:18:24 +01:00
jschaul
966ebe0169
Add support for Lenient|Strict Capture 2019-03-18 16:43:10 +01:00
Oleg Grenrus
ad0228030f
Merge pull request #1146 from cohei/fix-url-in-changelog
Fix URLs to pull requests in servant-server/CHANGELOG.md
2019-03-06 19:37:11 +02:00
TANIGUCHI Kohei
7062a842c3 Fix URLs to pull requests in servant-server/CHANGELOG.md 2019-03-07 02:30:41 +09:00
Oleg Grenrus
94e0acc3d2
Merge pull request #1145 from haskell-servant/ghc-8.6.4
Use ghc-8.6.4 on travis
2019-03-06 13:36:46 +02:00
Oleg Grenrus
e52e11a0ad Use ghc-8.6.4 on travis 2019-03-06 12:39:18 +02:00
Alp Mestanogullari
bdbc602ff9
Merge pull request #1143 from haskell-servant/alp/servant.dev
point to www.servant.dev and docs.servant.dev
2019-03-06 08:16:33 +01:00
Alp Mestanogullari
a386dd2095 point to www.servant.dev (website) and docs.servant.dev (self-explanatory) 2019-03-02 10:08:03 +01:00
Oleg Grenrus
960aee0dbe
Merge pull request #1141 from haskell-servant/re-enable-recipes-2
Re-enable some recipes (servant-0.16)
2019-02-28 14:58:33 +02:00
Oleg Grenrus
b28d37a7c5 Re-enable some recipes (servant-0.16) 2019-02-28 12:48:37 +02:00
Oleg Grenrus
cae428d845
Merge pull request #1140 from domenkozar/typo
[skip ci] fix typo in generic Client
2019-02-28 10:49:28 +02:00
Oleg Grenrus
b17c8bb8bd It's year 2019 2019-02-27 18:13:35 +02:00
Oleg Grenrus
cac12eae93 Bump version of servant-http-streams 2019-02-27 18:10:15 +02:00
Oleg Grenrus
bf766fbc8f
Merge pull request #1139 from phadej/servant-server-modules
Split RouteApplication mega-module
2019-02-27 15:33:01 +02:00
Oleg Grenrus
48c5cc96a2 Split RouteApplication mega-module 2019-02-27 15:06:56 +02:00
Domen Kožar
23c5e6a794
[skip ci] fix typo in generic Client 2019-02-27 19:54:02 +07:00
Oleg Grenrus
f3e294e341
Merge pull request #1138 from declension/patch-1
Fix typo in docs
2019-02-27 13:07:39 +02:00
Nick Boultbee
0869725b99
Fix typo in docs 2019-02-27 09:23:36 +00:00
Oleg Grenrus
f17a468872
Merge pull request #1136 from haskell-servant/travis-regenerate
Travis regenerate
2019-02-25 23:00:19 +02:00
Oleg Grenrus
733a19a76e Travis regenerate 2019-02-25 22:37:04 +02:00
Oleg Grenrus
0a5e649854
Merge pull request #1135 from haskell-servant/sibling-changelogs
Copy changelog entries to other packages
2019-02-22 09:50:41 +02:00
Oleg Grenrus
670b9527f8 Copy changelog entries to other packages 2019-02-21 13:31:24 +02:00
Oleg Grenrus
73a4d1fef4
Merge pull request #1134 from haskell-servant/bump-test-deps
Bump tasty and hspec
2019-02-21 10:51:52 +02:00
Oleg Grenrus
d03de86954 Bump tasty and hspec 2019-02-21 10:36:38 +02:00
Oleg Grenrus
b696f8202f
Merge pull request #1133 from haskell-servant/version-0.16
Bump versions to 0.16
2019-02-20 21:07:22 +02:00
Oleg Grenrus
58b401558d Bump versions to 0.16 2019-02-20 19:06:10 +02:00
Oleg Grenrus
eb2c4b6e07
Merge pull request #1132 from haskell-servant/changelog-0.16
Add CHANGELOG for 0.16
2019-02-20 12:31:04 +02:00
Oleg Grenrus
636420d2d1 Add CHANGELOG for 0.16 2019-02-20 01:48:38 +02:00
Oleg Grenrus
3db3d38e14
Merge pull request #1131 from phadej/error-renaming
Rename ServantError to ClientError, ServantErr to ServerError
2019-02-18 23:15:09 +02:00
Oleg Grenrus
a5655f8d5a Rename ServantError to ClientError, ServantErr to ServerError 2019-02-18 22:51:09 +02:00
Oleg Grenrus
e922b9898c
Merge pull request #1130 from phadej/refactor-servant-client
Refactor servant-client-core
2019-02-18 19:44:49 +02:00
Oleg Grenrus
775b55316c Try with reorder-goals: True 2019-02-18 19:26:25 +02:00
Oleg Grenrus
420ebd0475 Refactor servant-client-core
- No more Internal modules
- Remove ClientLike-generic. Let's use Routes-generics
    - Let's see if anyone notices, otherwise we can add it back
- Add Makefile for common tasks
    - Fix servant-client-ghcjs
2019-02-18 19:08:13 +02:00
Oleg Grenrus
c4620195d8
Merge pull request #1127 from domenkozar/patch-1
README: release branches are now protected with a wildcard
2019-02-18 17:00:13 +02:00
Domen Kožar
0e54459bd1
README: release branches are now protected with a wildcard 2019-02-18 14:58:54 +00:00
Oleg Grenrus
7e3853d030
Merge pull request #1124 from domenkozar/docs
Bump docs dependencies and move docs docs to README
2019-02-12 10:43:54 +02:00
Oleg Grenrus
132f7eb3d6
Merge pull request #1123 from haskell-servant/travis-update-2
Update travis once again
2019-02-12 10:43:19 +02:00
Domen Kožar
53b3b939e4
Bump docs dependencies and move docs docs to README 2019-02-12 13:38:25 +07:00
Oleg Grenrus
525b74c6f4 Update travis once again
- Update GHC-8.6.2 to GHC-8.6.3
- Build cookbook only with GHC-8.4.4 and GHC-8.6.3, thus
  making 8.0 and 8.2 jobs a little faster
2019-02-12 01:33:30 +02:00
Oleg Grenrus
32ccc2dcfa
Merge pull request #1121 from haskell-servant/update-travis
Add optimization: False
2019-02-11 16:24:07 +02:00
Oleg Grenrus
0e191964ae Add optimization: False 2019-02-11 15:52:42 +02:00
Oleg Grenrus
9e628355d7
Merge pull request #1119 from vaibhavsagar/vs-travis-xenial
.travis.yml: use Ubuntu 16.04
2019-02-09 08:05:22 +02:00
Oleg Grenrus
ccaa73fea3
Merge pull request #1117 from haskell-servant/servant-http-streams
WIP: servant-http-streams
2019-02-07 12:58:42 +02:00
Vaibhav Sagar
12ebc03187 .travis.yml: use Ubuntu 16.04 2019-02-06 14:18:24 -05:00
Oleg Grenrus
b44335ab69 Add servant-http-streams 2019-02-06 12:15:30 +02:00
Oleg Grenrus
4fab471c29 Refactor servant-client(-core)
- Rename GenResponse to ResponseF (analogous to RequestF)
- add NFData Headers
- Make Request and Response bodies be SourceIO,
  i.e. move conversions into specific implementations
2019-02-06 12:15:20 +02:00
Oleg Grenrus
2071042ebb
Merge pull request #1115 from phadej/1113-structured-connecition-error
keep structured exceptions in `ConnectionError`
2019-02-05 14:40:02 +02:00
Oleg Grenrus
9cc73f29ff Differentiate different exception types 2019-02-05 12:58:35 +02:00
Clement Delafargue
82a2c1f463 keep structured exceptions in ConnectionError
fixes #807
Previously, there were two levels of `SomeException` (see #714). A
test makes sure there is only one level of wrapping.
2019-02-05 12:46:06 +02:00
Oleg Grenrus
fdd1c7392b
Merge pull request #1114 from phadej/bgamari-request-in-failureresponse
Bgamari request in failureresponse
2019-02-05 12:25:50 +02:00
Oleg Grenrus
388f8f07cd Don't edit changelog 2019-02-05 11:53:06 +02:00
Oleg Grenrus
3a9a1ca55b Parametrise over body contents only 2019-02-05 11:51:42 +02:00
Oleg Grenrus
46afc9bcdd
Merge pull request #1110 from haskell-servant/oleg-testcase-1091
Oleg testcase 1091
2019-02-04 18:06:48 +02:00
Ben Gamari
9a655fd68e client: Preserve failing request in FailureResponse
This was previously implemented in #470 but later unintentionally
reverted in #803. This isn't verbatim the design implemented earlier; we
now capture the full RequestF save the request body.

Fixes #978.
2019-02-03 12:09:18 -05:00
Ben Gamari
aa704596be client: Parameterize RequestF on request body type
This allows us to provide an NFData instance for RequestF, which will
later be useful when we capture the request in FailureResponse.
2019-02-03 12:06:30 -05:00
Oleg Grenrus
109f7b2a45 Add venv warning checks to doc/Makefile 2019-02-02 16:05:03 +02:00
Oleg Grenrus
e9466b7752 Implement HasClient (StreamBody ... :> api) 2019-02-02 15:50:23 +02:00
Domen Kožar
e49b0369c0 servant-client: add a test case for StreamBody 2019-02-02 11:01:26 +02:00
Oleg Grenrus
b31128a99e Add changelog entries: 1104 1103 2019-02-02 10:25:24 +02:00
Oleg Grenrus
df6e992675
Merge pull request #1103 from tstat/capture-parse-failure-fix
Set http failure code priority explicitly
2019-02-02 10:23:55 +02:00
Oleg Grenrus
28ac8c072b
Merge pull request #1104 from michaelsdunn1/master
Update CookieJar with intermediate request/responses using Network.HTTP.Client.HistoriedResponse.
2019-02-02 10:21:21 +02:00
Michael Dunn
8490ccbe93 Do one atomic update to the cookie jar for all request and responses. 2019-01-26 22:13:32 -06:00
Michael Dunn
c33f27de04 updateCookieJar is now in STM to only allow for a single atomic update. 2019-01-26 18:01:53 -06:00
Michael Dunn
07b3236eb6 Added Cookie in CookieJar after redirect test case to ClientSpec.hs. 2019-01-25 10:17:58 -06:00
Oleg Grenrus
15610f24bd
Merge pull request #1107 from haskell-servant/network-3.0
Allow network-3.0
2019-01-23 10:28:47 +02:00
Oleg Grenrus
b685efecbe Allow network-3.0 2019-01-23 02:20:16 +02:00
Oleg Grenrus
c25085e9e5 Allow http-client-0.6 2019-01-09 10:45:40 +02:00
Travis Staton
9d8a8118b8 Set http failure code priority explicitly 2019-01-06 11:02:38 -05:00
Michael Dunn
37a38d7a9b Cookies are added to the CookieJar for all intermediate/redirected requests. 2019-01-04 17:04:20 -06:00
Alp Mestanogullari
a3d335b436
Merge pull request #1098 from csamak/patch-1
Fix the tutorial app4 curl example
2018-12-19 10:40:30 +01:00
csamak
2191fc815e
Fix the tutorial app4 curl example
Change the app4 curl example to match the API definition.
2018-12-17 11:11:41 -08:00
Oleg Grenrus
7bbcfb21e7
Merge pull request #1090 from domenkozar/nocontent-nfdata
Add NoContent instance for NFData
2018-11-27 19:48:59 +02:00
Domen Kožar
e7655d380a
Add NoContent instance for NFData 2018-11-27 16:41:31 +00:00
Oleg Grenrus
e3e5d2b230
Merge pull request #1082 from phadej/changelog-0.15b
Changelog 0.15b
2018-11-13 17:55:18 +02:00
Oleg Grenrus
3b8db040f3 http-api-data-0.4 2018-11-13 17:21:29 +02:00
Alp Mestanogullari
1fb70d3a90
Merge pull request #1081 from xaviershay/rawm
Add note to custom monad documentation about what to do with Raw endpoints
2018-11-13 10:53:07 +01:00
Oleg Grenrus
e0e0674645 Strict dependencies on http-api-data and singleton-bool 2018-11-13 10:35:06 +02:00
Oleg Grenrus
a1a99552b5 Changelog and cabal file edits
- Add #1079, #1011 entry
- Stricter internal dependencies
- Unify .cabal files
- Correct -conduit, -machines, -pipes changelog
- Fix years in LICENSEs
- Remove tinc.yamls
- Tweaks to 0.15 changelog
- Add changelogs for all packages
- Add changelogs for new packages (extra-source-files)
2018-11-13 09:58:42 +02:00
Oleg Grenrus
c62721e2ea
Merge pull request #1072 from phadej/changelog-0.15
Add changelog for 0.15
2018-11-12 22:01:55 +02:00
Oleg Grenrus
44aabebb04
Merge pull request #1079 from haskell-servant/issue-1011
Fix issue #1011: NewlineFraming encodes newline after each element
2018-11-12 22:01:14 +02:00
Oleg Grenrus
d64c2322d8
Merge pull request #1078 from phadej/arbitrary-sourcet
Add Arbitrary (SourceT m a) and StepT m a instances
2018-11-12 22:01:00 +02:00
Xavier Shay
63d685adb3 Add note to custom monad documentation about what to do with Raw endpoints. 2018-11-11 09:31:57 -08:00
Oleg Grenrus
f1eb5f93a8 Fix issue #1011: NewlineFraming encodes newline after each element 2018-11-10 01:45:56 +02:00
Oleg Grenrus
17f9237980 Add framingRender examples 2018-11-10 01:44:40 +02:00
Oleg Grenrus
4961cc2f3a Add Arbitrary (SourceT m a) and StepT m a instances
The generated instances are pure-ish; errorless.
2018-11-09 23:49:25 +02:00
Oleg Grenrus
ce83e4b404
Merge pull request #1077 from phadej/stream-body-mods
Add mods to StreamBody
2018-11-09 23:10:13 +02:00
Oleg Grenrus
c6311be5a1 Add #1077 entry 2018-11-09 21:52:20 +02:00
Oleg Grenrus
3001ed7990 Add mods to StreamBody 2018-11-09 21:49:53 +02:00
Oleg Grenrus
a5d3f44f2a Add #1076 entry 2018-11-09 21:14:30 +02:00
Oleg Grenrus
d9dc894ca6 Add changelog for 0.15 2018-11-09 21:14:30 +02:00
Oleg Grenrus
bbf196717f
Merge pull request #1076 from phadej/run-streaming-client
Add runClientM for streaming-client
2018-11-09 21:13:05 +02:00
Oleg Grenrus
0dd8ee7585 Add runClientM for streaming-client 2018-11-09 20:22:47 +02:00
Oleg Grenrus
5f947d1c43 Bump version numbers
- Almost everything 0.15; also servant-foreign jumped to 0.15, for
  consistency
- Bump lower bounds of dependencies to most recent versions atm
- Use hspec-2.6.0
- Update `stack.yaml` accordingly
- Use base-compat a bit more
- Drop aeson-compat dependency (in tests and tutorial)
2018-11-09 20:20:26 +02:00
Oleg Grenrus
5e3faa12c0
Merge pull request #1073 from phadej/ghc-8.6.2
tested-with: GHC==8.6.2
2018-11-09 18:49:45 +02:00
Oleg Grenrus
4195fd04d4 tested-with: GHC==8.6.2 2018-11-09 17:41:00 +02:00
Oleg Grenrus
97bd6f0a40
Merge pull request #1066 from phadej/separate-streaming-client
Separate streaming client
2018-11-09 14:50:57 +02:00
Oleg Grenrus
c95b195eb4 XhrClient doesn't support streaming 2018-11-08 18:47:54 +02:00
Oleg Grenrus
21af9a4051 Tweak lookupResponseHeader docs 2018-11-08 18:36:31 +02:00
Oleg Grenrus
db80f41dee
Merge pull request #1064 from DanBurton/lookup-response-header
Add lookupResponseHeader
2018-11-08 18:36:09 +02:00
Oleg Grenrus
8feda81fcd Separate Servant.Client.Streaming
- as a bonus only `servant-client` depends on `kan-extensions`
2018-11-08 17:58:21 +02:00
Oleg Grenrus
05d0f7e460
Merge pull request #1071 from phadej/golden-servant-docs
Golden servant docs
2018-11-08 17:37:19 +02:00
Oleg Grenrus
953747b55b Correct some doctest looking things to be doctests 2018-11-08 17:12:20 +02:00
Oleg Grenrus
7bed805cf7 Golden test for comprehensive API docs 2018-11-08 16:32:10 +02:00
Oleg Grenrus
f7bda98b6a Use tasty for servant-docs tests 2018-11-08 15:35:48 +02:00
Oleg Grenrus
80a047d1d4
Merge pull request #1035 from afcady/multipart-foreign
Implement HasForeign instance
2018-11-07 14:34:58 +02:00
Oleg Grenrus
da2af9fd5a
Merge pull request #1070 from phadej/test-public
Make ComprehensiveAPI part of public API
2018-11-06 18:38:02 +02:00
Oleg Grenrus
7634e08352 Make ComprehensiveAPI part of public API
We cannot simply tweak it, it will break tests of other packages.
Including packages not in this repository.
2018-11-06 13:35:47 +02:00
Alp Mestanogullari
56d95ae1ea
Merge pull request #1068 from cocreature/monadmask
Add MonadMask instance for Handler
2018-11-06 07:36:41 +01:00
Oleg Grenrus
3f56b86218
Merge pull request #1069 from phadej/re-enable-recipes
Re-enable some previously GHC-8.6 blocked recipes
2018-11-05 23:44:48 +02:00
Oleg Grenrus
f63610a513 base-compat in hoist-server-with-context 2018-11-05 19:20:18 +02:00
Oleg Grenrus
79caafe3fd Re-enable some previously GHC-8.6 blocked recipes 2018-11-05 17:52:01 +02:00
Oleg Grenrus
e94919f4b3
Merge pull request #991 from phadej/servant-machines
Streaming refactoring
2018-11-05 16:23:15 +02:00
Oleg Grenrus
45c1cbdfd5 Refactor Stream stuff
- Introduce SourceT, which is simple variant of "correct `ListT`".
  There are another variants possible (like in `streaming`),
  but I'm not sure there's much real difference.

- Introduce `Codensity`. There's a flag if people don't want to depend
  on `kan-extensions`.

- `StreamGenerator` and `ResultStream` are both `SourceT`.
  `Stream` combinator in `servant-client` uses `Codensity` for CPS.

- Add servant-machines, servant-conduit, servant-pipes
- Add streaming cookbook: just code, no explanations.
- Add a script to run streaming 'benchmarks'
2018-11-05 15:48:47 +02:00
Moritz Kiefer
95e66fa398 Add MonadMask instance for Handler 2018-11-03 21:41:23 +01:00
Oleg Grenrus
58ccae1ca0
Merge pull request #1043 from rsoeldner/fix-markdown
Fix markdown indentation and compilation warning
2018-10-31 18:50:21 +02:00
Dan Burton
e604b930dc
Add lookupResponseHeader 2018-10-28 01:36:40 -04:00
Oleg Grenrus
79bbcaf819
Merge pull request #1058 from k-bx/1055-custom-monad
genericServeT and docs on using a custom monad with generics
2018-10-26 13:50:19 +03:00
Oleg Grenrus
b440691d8e Add more (test) extra-deps to stack.yaml, thanks to k-bx 2018-10-26 13:48:01 +03:00
Kostiantyn Rybnikov
9d06e42525 Add ReaderT import 2018-10-26 13:05:46 +03:00
Kostiantyn Rybnikov
5620d2d781 Rename one function, run custom monad code from main 2018-10-26 13:05:46 +03:00
Kostiantyn Rybnikov
c1e15ef4c3 genericServeT and docs on using a custom monad with generics 2018-10-26 13:05:46 +03:00
Oleg Grenrus
56c2f4eda3
Merge pull request #1062 from phadej/enable-testing-recipe
Enable testing recipe
2018-10-26 13:01:52 +03:00
Oleg Grenrus
a4151acf9e Enable testing recipe 2018-10-25 23:16:16 +03:00
Oleg Grenrus
28f0cdb5d2 Testing recipe works only on GHC-8.4. Someone please fix it. 2018-10-23 15:52:00 +03:00
Alp Mestanogullari
98f039a126
Merge pull request #1059 from alexryndin/patch-1
Remove redundant point
2018-10-22 21:11:26 +02:00
Alex Ryndin
0675c62c38
Remove redundant point
# Remove redundant point
There are so much waste in the universe and we need to change the situation. The better usage the better habits of one leads a positive attitude of everyone.
# Improve disk space usage
You always hear guys complain about the cost of storage so it's appreciated as fuck. Decreasing line by 1 symbol leads to less disk space usage (up to 2 bytes at a time)!
2018-10-22 17:32:06 +03:00
Oleg Grenrus
7561b55e14
Merge pull request #1052 from domenkozar/export-GetHeaders-prime-master
ResponseHeaders: export GetHeaders'
2018-10-15 21:09:35 +03:00
Oleg Grenrus
2e5f8e7c5f Add cabal.ghcjs.project 2018-10-15 19:24:26 +03:00
Oleg Grenrus
788e0248f1 8.4.4 job 2018-10-15 18:15:52 +03:00
Alp Mestanogullari
9d63445fbf
Merge pull request #1050 from erewok/testing-cookbook
Add Testing cookbook
2018-10-15 07:05:30 +02:00
Erik Aker
bb365159f3 Pull out instance for hopefully more clarity 2018-10-14 13:27:33 -07:00
Erik Aker
02da07f95f small typo cleanups 2018-10-14 11:04:37 -07:00
Erik Aker
64f89f600a Use a different server-running method and add bulleted list of strategies at start 2018-10-14 10:19:49 -07:00
Domen Kožar
c7f616ea2d
ResponseHeaders: export GetHeaders' 2018-10-14 12:15:41 +01:00
Oleg Grenrus
26b98f0e95
Merge pull request #1046 from phadej/fosskers/ghc-86-bounds
Fosskers/ghc 86 bounds
2018-10-14 11:41:59 +03:00
Erik Aker
0d0fd0de82 Clean up the comments 2018-10-13 06:35:56 -07:00
Erik Aker
18456baac5 Clean up the language so it reads better 2018-10-12 17:32:20 -07:00
Erik Aker
89336aee96 Make tests run and include failings for illustrative purposes 2018-10-12 08:48:25 -07:00
Erik Aker
43af1d0c9e WIP Commit: must finish servant-quickcheck tests 2018-10-11 20:51:30 -07:00
Oleg Grenrus
45e7d58d77 Fix travis
Disable recipes:
- hoist-server-with-context
- jwt-and-basic-auth
- pagination

Add allow-newer:
- servant-js:base
2018-10-08 09:58:01 +03:00
Colin Woodbury
7150a5bec3
Remove Travis references to tutorial
- It depends on `servant-js`, which is behind on its `base` bound.
2018-10-07 13:12:18 -07:00
Colin Woodbury
c1ef734fa0
Jigger some base bounds for CI 2018-10-07 13:02:37 -07:00
Colin Woodbury
7428b76dd6
Remove Travis references to jwt-and-basic-auth 2018-10-07 12:40:31 -07:00
Colin Woodbury
42aac2bb52
Remove Travis references to hoist-server-with-context
- This is in an attempt to get cookbooks building with GHC 8.6.
2018-10-07 12:30:31 -07:00
Colin Woodbury
b2ed29b0b9
Update Travis config for GHC 8.6 2018-10-07 11:33:05 -07:00
Colin Woodbury
ef573bab0e
Update tested-with fields 2018-10-07 10:44:41 -07:00
Colin Woodbury
dec0636611
Update bounds for GHC 8.6 2018-10-07 10:38:36 -07:00
Oleg Grenrus
80c1ec14c8 Regenerate .travis.yml 2018-10-04 13:04:00 +03:00
Oleg Grenrus
e87bf9b600
Merge pull request #1044 from erewok/hoistWithContextCookbook
Add new cookbook recipe for hoistServerWithContext
2018-10-04 12:22:53 +03:00
Erik Aker
0c4dc88592 Add new cookbook recipe for hoistServerWithContext 2018-10-03 18:00:06 -07:00
Robert Soeldner
4e5e0a5324 Update example output 2018-09-29 17:50:23 +02:00
Robert Soeldner
a2f8c2c9b4 Fix markdown code indent, drop unused var 2018-09-29 17:49:06 +02:00
Oleg Grenrus
c85f99f2b1
Merge pull request #1039 from phadej/sentry-travis
Regenerate .travis.yml
2018-09-19 14:04:51 +03:00
Oleg Grenrus
e03b568b41 Regenerate .travis.yml 2018-09-19 13:27:43 +03:00
Alp Mestanogullari
79f8858dfc
Merge pull request #987 from marcosh/sentry-cookbook
cookbook sentry integration page
2018-09-19 10:15:27 +02:00
Marco Perone
c5d867cfb1 remove changelog entry 2018-09-19 08:25:42 +02:00
Marco Perone
3306e6ff3c add missing show on sentry cookbook 2018-09-19 08:25:42 +02:00
Marco Perone
5a65581beb added changelog and CI steps for Sentry recipe 2018-09-19 08:25:42 +02:00
Marco Perone
7a9504e046 cookbook sentry integration page 2018-09-19 08:21:09 +02:00
Oleg Grenrus
2c70a2b3f5
Merge pull request #1037 from phadej/some-lift
Add aeson and Lift BaseUrl instances
2018-09-18 14:35:38 +03:00
Oleg Grenrus
a956abddeb Add aeson and Lift BaseUrl instances 2018-09-18 13:47:23 +03:00
Andrew Cady
c07f86bda7 introduce type ReqBodyContentType replacing use of Bool 2018-09-17 17:22:32 -04:00
Oleg Grenrus
8cb3f2e402
Merge pull request #1036 from haskell-servant/alp/contributing
CONTRIBUTING.md: ask contributors to leave the changelogs and package versions untouched
2018-09-17 23:05:20 +03:00
Andrew Cady
19378364dc revert version bump added in previous commit 2018-09-17 13:37:08 -04:00
Alp Mestanogullari
d494a50e94
CONTRIBUTING.md: ask contributors to leave the changelogs and package versions untouched 2018-09-17 17:09:52 +02:00
Andrew Cady
62def38a9b Add record to Req type
This is needed for servant-js to know whether to call JSON.stringify()
on the request body or just send it raw.
2018-09-16 21:08:07 -04:00
Alp Mestanogullari
af7ba3d6b8
Merge pull request #1033 from CurrySoftware/master
curl-mock cookbook example
2018-09-15 13:03:21 +02:00
Jakob Demler
8336fc96d5 fix some curl-mock typos 2018-09-15 01:36:09 +02:00
Jakob Demler
765b62b05b curl-mock cookbook example 2018-09-14 19:01:04 +02:00
Oleg Grenrus
e066fbe493
Merge pull request #1032 from Taneb/master
Add bifunctors instances for Servant.API.Alt
2018-09-07 13:18:39 +03:00
Nathan van Doorn
43a1d586fe Raise lower bound on bifunctors to match Stackage LTS 2018-09-07 09:58:09 +01:00
Nathan van Doorn
7133e9dad2 Add bifunctors instances for Servant.API.Alt
These mirror the corresponding instances for (,)
2018-09-05 13:15:42 +01:00
Alp Mestanogullari
b8f7eb4452
Merge pull request #1030 from aRkadeFR/doc-full-example-servant-elm-auth-yeshql-postgresql
Documentation, adding full website project with servant,auth,elm,yeshql,postgresql
2018-08-30 00:04:03 +02:00
aRkadeFR
6211c0c70b Full website project with servant,auth,elm,yeshql,postgresql 2018-08-29 18:40:57 +02:00
Oleg Grenrus
46973b7ccf
Merge pull request #1024 from messis/add-PutCreated-verb
Add put created verb
2018-08-16 20:13:09 +03:00
messis
9df6b1f2a5 Add PutCreated verb 2018-08-13 15:26:34 +02:00
messis
13b521eb90 Add PutCreated verb 2018-08-13 15:15:45 +02:00
Alp Mestanogullari
f75bbf25be
Merge pull request #1020 from maxhbr/maxhbr/fixMinorTypo
Fix minor typo in deprecation warning
2018-08-04 22:40:24 +02:00
Maximilian Huber
81f4db6c5c fix minor typo in deprecation warning 2018-08-04 17:16:02 +02:00
Oleg Grenrus
f6c07f30d5
Merge pull request #1010 from haskell-servant/readme-version-history
Remove version history from README.md
2018-07-13 10:59:42 +03:00
Alp Mestanogullari
3ca4b61148
Remove version history from README.md
It is out of date and we already have enough to do during releases to be updating this table, I think.
2018-07-13 09:52:03 +02:00
Alp Mestanogullari
5aa22866f2
fix github code search link 2018-07-11 16:51:50 +02:00
Oleg Grenrus
99e535b579
Merge pull request #1009 from phadej/cleanup-pre-ghc-8.0
Cleanup pre-GHC-8.0 stuff
2018-07-11 10:10:43 +03:00
Oleg Grenrus
cfade67c2f Cleanup pre-GHC-8.0 stuff 2018-07-11 01:39:38 +03:00
Oleg Grenrus
44d34dfd9b
Merge pull request #1007 from haskell-servant/github-search
Point to github search for "import Servant" in the examples page
2018-07-11 01:32:49 +03:00
Oleg Grenrus
720bb40645
Merge pull request #1008 from phadej/lower-ghc-8.0
Support only GHC-8.0+
2018-07-11 01:12:26 +03:00
Oleg Grenrus
5ba0e439dc Support only GHC-8.0+
Let's bump lower bounds to whatever is in LTS close to release date.
2018-07-10 17:17:56 +03:00
Alp Mestanogullari
749eb61aef
Point to github search for servant in the examples page 2018-07-09 14:04:49 +02:00
Oleg Grenrus
0ccf698800 Add simple setup.py for RTD 2018-07-06 12:02:00 +03:00
Oleg Grenrus
a7fc453ee1 Fix typo 2018-07-06 11:59:16 +03:00
Oleg Grenrus
4b247ee801 Update doc deps 2018-07-06 11:57:30 +03:00
Oleg Grenrus
bd8c5b96c3
Merge pull request #1005 from haskell-servant/using-free-client
Add 'using free client' recipe
2018-07-06 04:18:19 +03:00
Alp Mestanogullari
4eca451f2c
oops 2018-07-06 01:50:58 +02:00
Alp Mestanogullari
ab68ff8ae2
remove credits to @phadej 2018-07-06 01:48:12 +02:00
Alp Mestanogullari
66039fd124
tweaks 2018-07-06 00:38:22 +02:00
Alp Mestanogullari
fbd9f3ec29
tentative improvements 2018-07-06 00:36:37 +02:00
Oleg Grenrus
8dc323ef0a Add 'using free client' recipe 2018-07-06 00:21:17 +03:00
Oleg Grenrus
f536c90fa5 Disable flawed streams in constant memory test 2018-07-05 23:39:02 +03:00
Oleg Grenrus
1114925048 Allow free-5.1, lens-4.17 2018-07-05 23:21:36 +03:00
Oleg Grenrus
2ec3596c56 Add generic/Generic.lhs to cookbook/index.rst 2018-07-05 23:21:32 +03:00
Oleg Grenrus
88f8d3b0d1 Merge servant-generic 2018-07-05 23:21:25 +03:00
Oleg Grenrus
374a7b88fb Deprecate S.Utils.StaticFiles in favor of S.Server.StaticFiles 2018-07-05 23:20:59 +03:00
Oleg Grenrus
187c3f49d2
Merge pull request #938 from LumiGuide/feat-binary-requests
servant-client-ghcjs: Support binary requests
2018-07-01 11:03:22 +03:00
Oleg Grenrus
319dcc2fe1 stylish-haskell servant-client-core 2018-06-30 22:17:08 +03:00
Oleg Grenrus
07716b8f4e
Merge pull request #1001 from phadej/stylish-haskell-all
Apply stylish-haskell on all modules
2018-06-30 00:40:22 +03:00
Oleg Grenrus
f9bcc15d0b Apply stylish-haskell on all modules 2018-06-29 23:36:39 +03:00
Oleg Grenrus
e3c91ec579
Merge pull request #998 from phadej/issue-997
Move Servant.Utils.Links -> Servant.Links. Fixes #997.
2018-06-25 15:00:28 +03:00
Oleg Grenrus
2c02287b6b Move Servant.Utils.Links -> Servant.Links. Fixes #997. 2018-06-25 14:27:17 +03:00
Oleg Grenrus
5854071641
Merge pull request #996 from phadej/remove-enter
Remove Servant.Utils.Enter
2018-06-25 01:25:10 +03:00
Oleg Grenrus
7150f2b603 Remove Servant.Utils.Enter 2018-06-24 22:56:30 +03:00
Oleg Grenrus
ae05ef5312
Merge pull request #988 from phadej/list-is-pure-stream
Add toStreamGenerator (NonEmpty a) instance
2018-06-24 22:56:07 +03:00
Oleg Grenrus
dcc67f3089 Add FromResultStream/ToStreamGenerator [a] instances.
- Add Streaming endpoint to the comprehensive API.
- Rename BuildFromStream to FromResultStram
   - I'm tempted to rename everything in the Servant.API.Stream
     (add some prefixes, `header` is too good name to steal)

The TODO in `servant-docs` is left intentionally.
2018-06-24 21:52:09 +03:00
Oleg Grenrus
77ea599c63
Merge pull request #985 from phadej/forward-port
Forward port
2018-06-19 22:27:34 +03:00
Oleg Grenrus
d45870d088 Fix cabal.make-travis-yml 2018-06-19 21:24:33 +03:00
Oleg Grenrus
85ed092873 Enable rest of recipes 2018-06-19 21:23:46 +03:00
Oleg Grenrus
bd40d46d28 Update x-revisions 2018-06-19 21:23:40 +03:00
Oleg Grenrus
ba3a2f7b87 Default-Language in servant-client 2018-06-19 21:23:35 +03:00
Oleg Grenrus
e1850175f7 Add changelogs to other packages 2018-06-19 21:23:28 +03:00
Oleg Grenrus
f75583dbf1 Bump some lower bounds
Also drop unused dependencies
2018-06-19 21:23:17 +03:00
Oleg Grenrus
e5529471ae Regenerate .travis.yml 2018-06-19 21:23:14 +03:00
Oleg Grenrus
2edaa5d95b
Merge pull request #974 from haskell-servant/alp/tutorial-tweaks
website/tutorial tweaks
2018-06-13 01:25:53 +03:00
Alp Mestanogullari
11928bcdd2 website/tutorial tweaks 2018-06-12 23:23:24 +03:00
Oleg Grenrus
507263e7e8 Add migration guide for hoistClientMonad 2018-06-12 22:05:30 +03:00
Oleg Grenrus
0c23287ed3 Add links to changelog of 0.14 2018-06-12 21:26:33 +03:00
Oleg Grenrus
b0fefac5c6 Fix travis 2018-06-12 20:46:39 +03:00
Oleg Grenrus
626762df7e Fix .travis.yml 2018-06-12 19:29:44 +03:00
Oleg Grenrus
6a1fa67fc4 Bump up versions 2018-06-12 19:27:13 +03:00
Oleg Grenrus
c793d377ef
Merge pull request #977 from phadej/changelog-0.14
Add changelog for 0.14
2018-06-12 19:25:46 +03:00
Oleg Grenrus
6b45942b90 Add changelog for 0.14 2018-06-12 18:51:02 +03:00
Oleg Grenrus
cc273f2d8b Allow aeson-1.4 2018-06-12 14:04:45 +03:00
Oleg Grenrus
116cf4f8b7
Merge pull request #973 from phadej/back-to-ppa-cabal
Use fixed cabal-install-2.2
2018-06-12 13:40:53 +03:00
Alp Mestanogullari
774a9f49b7
Merge pull request #975 from jml/run-capture-all-tests
Run `captureAllSpec`
2018-06-10 21:30:00 +02:00
Jonathan Lange
972ed49dd4 Run captureAllSpec
This was missed due to an oversight.
2018-06-10 17:38:22 +01:00
Oleg Grenrus
352596398e
Merge pull request #972 from phadej/stream-status-test
Add test for Stream status setting
2018-06-09 12:25:46 +03:00
Oleg Grenrus
0f4df5d429 Use fixed cabal-install-2.2 2018-06-09 10:15:48 +03:00
Oleg Grenrus
1614ca59bf Add test for Stream status setting 2018-06-09 09:37:05 +03:00
Oleg Grenrus
f53370b361
Merge pull request #966 from jvanbruegge/stream-code
Allow to specify a status for streaming endpoints
2018-06-09 08:42:54 +03:00
Oleg Grenrus
e04735c280
Merge pull request #971 from phadej/get-headers-no-overlap
Implement GetHeaders instances without overlapping
2018-06-08 16:18:35 +03:00
Oleg Grenrus
be42f3d608 Implement GetHeaders instances without overlapping 2018-06-08 15:10:38 +03:00
Oleg Grenrus
e1b848f67c
Merge pull request #968 from phadej/issue-952-safelink-prime
Add safeLink'
2018-06-01 16:47:33 +03:00
Oleg Grenrus
0b084afe62 Update .travis.yml
- Disable file-upload recipe (changed MkLink breaks released
  servant-multipart)
- GHC-8.4.2 -> GHC-8.4.3
2018-06-01 13:42:34 +03:00
Oleg Grenrus
46663f29b0 Add safeLink'
Resolves #952
2018-06-01 12:50:56 +03:00
Jan van Brügge
dbbe9b7321 Allow to specify the status of streaming endpoints 2018-05-28 12:00:29 +02:00
Oleg Grenrus
a66aa8a981
Merge pull request #959 from jvanbruegge/fix-stream
Change definition of StreamGenerator
2018-05-28 09:26:53 +03:00
Oleg Grenrus
64cb1f342f
Merge pull request #964 from phadej/ghc-8.4.2
Support GHC-8.4.1 and newer deps
2018-05-28 08:39:02 +03:00
Oleg Grenrus
e874beba18 Try to fix pagination sdist 2018-05-28 08:13:36 +03:00
Oleg Grenrus
c56fda3869 Support GHC-8.4.1 and newer deps 2018-05-26 01:06:36 +03:00
Jan van Brügge
a0b6d7a2de Update documentation 2018-05-24 05:08:48 +02:00
Jan van Brügge
b80a3e6279 Add NoFraming strategy 2018-05-24 05:08:48 +02:00
Jan van Brügge
0ba09c999b Change definition of StreamGenerator 2018-05-24 05:08:48 +02:00
Oleg Grenrus
3e8c2170d1
Merge pull request #963 from muattiyah/typo-fix
Fix typo in a comment in the Stream module.
2018-05-23 21:02:22 +03:00
Muhammad Attiyah
8cb0d4817e Fix typo in a comment in the Stream module. 2018-05-23 18:15:45 +02:00
Oleg Grenrus
3c4790ea27
Merge pull request #961 from phadej/grayjay-cabal-patch
Try grayjay patchto fix 7.8.4 job
2018-05-23 17:57:11 +03:00
Oleg Grenrus
d073eb0619 Temporarily disable haddock in travis 2018-05-23 17:05:56 +03:00
Oleg Grenrus
397feed72a Try grayjay patchto fix 7.8.4 job 2018-05-23 16:06:42 +03:00
Alp Mestanogullari
422bf034a5
Merge pull request #960 from haskell-servant/alpmestan-patch-1
one more repository full of examples
2018-05-22 17:22:05 +02:00
Alp Mestanogullari
9fb4b87ac4
Update examples.md 2018-05-22 17:21:47 +02:00
Alp Mestanogullari
40bc0f2983
one more repository full of examples 2018-05-22 17:20:34 +02:00
Alp Mestanogullari
f88cfd0740
Merge pull request #958 from chreekat/patch-1
Remove duplicate type declaration in server tutorial
2018-05-12 23:42:01 +02:00
Bryan Richter
abd11b2a8f
Remove duplicate type declaration
UserAPI1 is already defined at line 64.
2018-05-12 16:58:43 -04:00
Falco Peijnenburg
4df71dce96 servant-client-ghcjs: Throw exception on streamingRequest
Documented this behaviour in haddocks of client and ClientM
2018-04-28 14:36:47 +02:00
Falco Peijnenburg
108df0857e servant-client-ghcjs: Support binary requests
Introduces support for both sending and receiving binary data
2018-04-28 13:42:26 +02:00
Alp Mestanogullari
0c66b9c055
Merge pull request #953 from haskell-servant/fileserving-polymorphic-monad
servant-server: make file-serving functions polymorphic in the monad
2018-04-26 06:56:25 +02:00
Alp Mestanogullari
fd21e92cf2 servant-server: make file-serving functions polymorphic in the monad 2018-04-25 15:12:32 +02:00
Oleg Grenrus
63253f09df
Merge pull request #948 from domenkozar/servant-client-readme
servant-client: update README.md and test it
2018-04-20 22:56:15 +03:00
Domen Kožar
49969695df
servant-client: update README.md and test it 2018-04-18 14:18:41 +01:00
Alp Mestanogullari
6af38354d0
Merge pull request #946 from KtorZ/servant-pagination
add cookbook recipe introducing servant-pagination
2018-04-17 10:33:28 +02:00
KtorZ
93838ae9e7
Add recipe introducing servant-pagination 2018-04-17 10:02:40 +02:00
Oleg Grenrus
680820c98c
Merge pull request #943 from phadej/bounds-backports
Bounds backports
2018-04-11 12:12:27 +03:00
Oleg Grenrus
7d4293fb56 Bump hspec, doctest and transformers-compat 2018-04-11 11:34:16 +03:00
Oleg Grenrus
9ddaafed42 Bump servant-client version 2018-04-11 11:28:33 +03:00
Oleg Grenrus
fc12109514 Allow temporary-1.3 2018-04-11 11:28:08 +03:00
Oleg Grenrus
dd7ec1dfd7 Allow base-compat-0.10.0 2018-04-11 11:26:14 +03:00
Oleg Grenrus
6be8291fe8
Merge pull request #936 from haskell-servant/alp/hoistClient
Add hoistClient to servant-client
2018-04-09 15:19:21 +03:00
Alp Mestanogullari
a155d5d497 changelog entries 2018-04-09 13:57:31 +02:00
Oleg Grenrus
a8cd6e3895
Merge pull request #937 from potomak/subserver-content-types
Update request content-type handling
2018-04-05 16:00:02 +03:00
Giovanni Cappellotto
92f8d2314e Update request content-type handling
In case that a sub-server doesn't support the content-type specified
in the request invoke `delayedFail` instead of `delayedFailFatal` in
order to give the chance to other sub-servers to handle the request.
2018-04-04 18:53:40 -04:00
Alp Mestanogullari
fc3c6089b8 document hoistClient (haddocks, tutorial) 2018-04-04 01:48:48 +02:00
Alp Mestanogullari
9eb57a6119 add a test for hoistClient 2018-04-04 01:48:48 +02:00
Alp Mestanogullari
200311ee26 add hoistClient to HasClient class 2018-04-04 01:48:48 +02:00
359 changed files with 23601 additions and 4902 deletions

12
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [arianvp]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://github.com/haskell-servant/haskell-servant.github.io/blob/hakyll/consultancies.md

14
.github/run-ghcjs-tests.sh vendored Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
#
# cabal v2-test does not work with GHCJS
# See: https://github.com/haskell/cabal/issues/6175
#
# This invokes cabal-plan to figure out test binaries, and invokes them with node.
cabal-plan list-bins '*:test:*' | while read -r line
do
testpkg=$(echo "$line" | perl -pe 's/:.*//')
testexe=$(echo "$line" | awk '{ print $2 }')
echo "testing $textexe in package $textpkg"
(cd "$testpkg" && node "$testexe".jsexe/all.js)
done

148
.github/workflows/master.yml vendored Normal file
View file

@ -0,0 +1,148 @@
name: CI
# Trigger the workflow on push or pull request, but only for the master branch
on:
pull_request:
push:
branches: [master]
jobs:
cabal:
name: ${{ matrix.os }} / ghc ${{ matrix.ghc }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
cabal: ["3.6"]
ghc:
- "8.6.5"
- "8.8.4"
- "8.10.7"
- "9.0.2"
- "9.2.2"
- "9.4.2"
steps:
- uses: actions/checkout@v2
- uses: haskell/actions/setup@v1
id: setup-haskell-cabal
name: Setup Haskell
with:
ghc-version: ${{ matrix.ghc }}
cabal-version: ${{ matrix.cabal }}
- name: Freeze
run: |
cabal configure --enable-tests --enable-benchmarks --test-show-details=direct
cabal freeze
- uses: actions/cache@v2.1.3
name: Cache ~/.cabal/store and dist-newstyle
with:
path: |
${{ steps.setup-haskell-cabal.outputs.cabal-store }}
dist-newstyle
key: ${{ runner.os }}-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
restore-keys: |
${{ runner.os }}-${{ matrix.ghc }}-
- name: Configure
run: |
cabal install --ignore-project -j2 doctest --constraint='doctest ^>=0.20'
- name: Build
run: |
cabal build all
- name: Test
run: |
cabal test all
- name: Run doctests
run: |
# Necessary for doctest to be found in $PATH
export PATH="$HOME/.cabal/bin:$PATH"
DOCTEST="cabal repl --with-ghc=doctest --ghc-options=-w"
(cd servant && eval $DOCTEST)
(cd servant-client && eval $DOCTEST)
(cd servant-client-core && eval $DOCTEST)
(cd servant-http-streams && eval $DOCTEST)
(cd servant-docs && eval $DOCTEST)
(cd servant-foreign && eval $DOCTEST)
(cd servant-server && eval $DOCTEST)
(cd servant-machines && eval $DOCTEST)
(cd servant-conduit && eval $DOCTEST)
(cd servant-pipes && eval $DOCTEST)
# stack:
# name: stack / ghc ${{ matrix.ghc }}
# runs-on: ubuntu-latest
# strategy:
# matrix:
# stack: ["2.7.5"]
# ghc: ["8.10.7"]
# steps:
# - uses: actions/checkout@v2
# - uses: haskell/actions/setup@v1
# name: Setup Haskell Stack
# with:
# ghc-version: ${{ matrix.ghc }}
# stack-version: ${{ matrix.stack }}
# - uses: actions/cache@v2.1.3
# name: Cache ~/.stack
# with:
# path: ~/.stack
# key: ${{ runner.os }}-${{ matrix.ghc }}-stack
# - name: Install dependencies
# run: |
# stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --only-dependencies
# - name: Build
# run: |
# stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks
# - name: Test
# run: |
# stack test --system-ghc
ghcjs:
name: ubuntu-latest / ghcjs 8.6
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v13
with:
extra_nix_config: |
trusted-public-keys = ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=1aba6f367982bd6dd78ec2fda75ab246a62d32c5 cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
substituters = https://nixcache.reflex-frp.org https://cache.nixos.org/
- name: Setup
run: |
# Override cabal.project with the lightweight GHCJS one
cp cabal.ghcjs.project cabal.project
cat cabal.project
nix-shell ghcjs.nix --run "cabal v2-update && cabal v2-freeze"
- uses: actions/cache@v2.1.3
name: Cache ~/.cabal/store and dist-newstyle
with:
path: |
~/.cabal/store
dist-newstyle
key: ${{ runner.os }}-ghcjs8.6-${{ hashFiles('cabal.project.freeze') }}
restore-keys: |
${{ runner.os }}-ghcjs8.6-
- name: Build
run: |
nix-shell ghcjs.nix --run "cabal v2-build --ghcjs --enable-tests --enable-benchmarks all"
- name: Tests
run: |
nix-shell ghcjs.nix --run ".github/run-ghcjs-tests.sh"

11
.gitignore vendored
View file

@ -1,5 +1,6 @@
**/*/dist **/*/dist
dist-newstyle *~
dist-*
.ghc.environment.* .ghc.environment.*
/bin /bin
/lib /lib
@ -29,3 +30,11 @@ doc/_build
doc/venv doc/venv
doc/tutorial/static/api.js doc/tutorial/static/api.js
doc/tutorial/static/jq.js doc/tutorial/static/jq.js
shell.nix
.hspec-failures
# nix
result*
# local versions of things
servant-multipart

View file

@ -1,171 +0,0 @@
# This Travis job script has been generated by a script via
#
# runghc make_travis_yml_2.hs '--config=cabal.make-travis-yml' '--output=.travis.yml' '--max-backjumps=10000' 'cabal.project'
#
# For more information, see https://github.com/hvr/multi-ghc-travis
#
language: c
sudo: false
git:
submodules: false # whether to recursively clone submodules
branches:
only:
- master
- release-0.12
cache:
directories:
- $HOME/.cabal/packages
- $HOME/.cabal/store
before_cache:
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
# remove files that are regenerated by 'cabal update'
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
- rm -rfv $HOME/.cabal/packages/head.hackage
matrix:
include:
- compiler: "ghc-7.8.4"
# env: TEST=--disable-tests BENCH=--disable-benchmarks
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-7.8.4], sources: [hvr-ghc]}}
- compiler: "ghc-7.10.3"
# env: TEST=--disable-tests BENCH=--disable-benchmarks
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-7.10.3], sources: [hvr-ghc]}}
- compiler: "ghc-8.0.2"
# env: TEST=--disable-tests BENCH=--disable-benchmarks
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.0.2], sources: [hvr-ghc]}}
- compiler: "ghc-8.2.2"
# env: TEST=--disable-tests BENCH=--disable-benchmarks
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.2.2], sources: [hvr-ghc]}}
before_install:
- HC=${CC}
- HCPKG=${HC/ghc/ghc-pkg}
- unset CC
- ROOTDIR=$(pwd)
- mkdir -p $HOME/.local/bin
- "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
- HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
- echo $HCNUMVER
install:
- cabal --version
- echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
- BENCH=${BENCH---enable-benchmarks}
- TEST=${TEST---enable-tests}
- HADDOCK=${HADDOCK-true}
- INSTALLED=${INSTALLED-true}
- GHCHEAD=${GHCHEAD-false}
- CABALNEWBUILDOPTS=--max-backjumps=10000
- travis_retry cabal update -v
- "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
- rm -fv cabal.project cabal.project.local
- "if [ $HCNUMVER -ge 70800 ]; then sed -i.bak 's/-- ghc-options:.*/ghc-options: -j2/' ${HOME}/.cabal/config; fi"
- grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
- "printf 'packages: \"servant\" \"servant-client\" \"servant-client-core\" \"servant-docs\" \"servant-foreign\" \"servant-server\" \"doc/tutorial\" \"doc/cookbook/db-postgres-pool\" \"doc/cookbook/jwt-and-basic-auth\" \"doc/cookbook/db-sqlite-simple\" \"doc/cookbook/basic-auth\" \"doc/cookbook/https\" \"doc/cookbook/structuring-apis\" \"doc/cookbook/using-custom-monad\" \"doc/cookbook/file-upload\"\\n' > cabal.project"
- "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
- "echo 'allow-newer: servant-js:servant,servant-js:servant-foreign,servant-auth-server:http-types,servant-multipart:lens,servant-multipart:resourcet,servant-multipart:servant,servant-multipart:servant-server,servant-auth-server:servant-server' >> cabal.project"
- cat cabal.project
- if [ -f "servant/configure.ac" ]; then
(cd "servant" && autoreconf -i);
fi
- if [ -f "servant-client/configure.ac" ]; then
(cd "servant-client" && autoreconf -i);
fi
- if [ -f "servant-client-core/configure.ac" ]; then
(cd "servant-client-core" && autoreconf -i);
fi
- if [ -f "servant-docs/configure.ac" ]; then
(cd "servant-docs" && autoreconf -i);
fi
- if [ -f "servant-foreign/configure.ac" ]; then
(cd "servant-foreign" && autoreconf -i);
fi
- if [ -f "servant-server/configure.ac" ]; then
(cd "servant-server" && autoreconf -i);
fi
- if [ -f "doc/tutorial/configure.ac" ]; then
(cd "doc/tutorial" && autoreconf -i);
fi
- if [ -f "doc/cookbook/db-postgres-pool/configure.ac" ]; then
(cd "doc/cookbook/db-postgres-pool" && autoreconf -i);
fi
- if [ -f "doc/cookbook/jwt-and-basic-auth/configure.ac" ]; then
(cd "doc/cookbook/jwt-and-basic-auth" && autoreconf -i);
fi
- if [ -f "doc/cookbook/db-sqlite-simple/configure.ac" ]; then
(cd "doc/cookbook/db-sqlite-simple" && autoreconf -i);
fi
- if [ -f "doc/cookbook/basic-auth/configure.ac" ]; then
(cd "doc/cookbook/basic-auth" && autoreconf -i);
fi
- if [ -f "doc/cookbook/https/configure.ac" ]; then
(cd "doc/cookbook/https" && autoreconf -i);
fi
- if [ -f "doc/cookbook/structuring-apis/configure.ac" ]; then
(cd "doc/cookbook/structuring-apis" && autoreconf -i);
fi
- if [ -f "doc/cookbook/using-custom-monad/configure.ac" ]; then
(cd "doc/cookbook/using-custom-monad" && autoreconf -i);
fi
- if [ -f "doc/cookbook/file-upload/configure.ac" ]; then
(cd "doc/cookbook/file-upload" && autoreconf -i);
fi
- rm -f cabal.project.freeze
- rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-client-core"/dist "servant-docs"/dist "servant-foreign"/dist "servant-server"/dist "doc/tutorial"/dist "doc/cookbook/db-postgres-pool"/dist "doc/cookbook/jwt-and-basic-auth"/dist "doc/cookbook/db-sqlite-simple"/dist "doc/cookbook/basic-auth"/dist "doc/cookbook/https"/dist "doc/cookbook/structuring-apis"/dist "doc/cookbook/using-custom-monad"/dist "doc/cookbook/file-upload"/dist
- DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
# Here starts the actual work to be performed for the package under test;
# any command which exits with a non-zero exit code causes the build to fail.
script:
# test that source-distributions can be generated
- echo Packaging... && echo -en 'travis_fold:start:sdist\\r'
- (cd "servant" && cabal sdist)
- (cd "servant-client" && cabal sdist)
- (cd "servant-client-core" && cabal sdist)
- (cd "servant-docs" && cabal sdist)
- (cd "servant-foreign" && cabal sdist)
- (cd "servant-server" && cabal sdist)
- (cd "doc/tutorial" && cabal sdist)
- (cd "doc/cookbook/db-postgres-pool" && cabal sdist)
- (cd "doc/cookbook/jwt-and-basic-auth" && cabal sdist)
- (cd "doc/cookbook/db-sqlite-simple" && cabal sdist)
- (cd "doc/cookbook/basic-auth" && cabal sdist)
- (cd "doc/cookbook/https" && cabal sdist)
- (cd "doc/cookbook/structuring-apis" && cabal sdist)
- (cd "doc/cookbook/using-custom-monad" && cabal sdist)
- (cd "doc/cookbook/file-upload" && cabal sdist)
- echo -en 'travis_fold:end:sdist\\r'
- echo Unpacking... && echo -en 'travis_fold:start:unpack\\r'
- mv "servant"/dist/servant-*.tar.gz "servant-client"/dist/servant-client-*.tar.gz "servant-client-core"/dist/servant-client-core-*.tar.gz "servant-docs"/dist/servant-docs-*.tar.gz "servant-foreign"/dist/servant-foreign-*.tar.gz "servant-server"/dist/servant-server-*.tar.gz "doc/tutorial"/dist/tutorial-*.tar.gz "doc/cookbook/db-postgres-pool"/dist/cookbook-db-postgres-pool-*.tar.gz "doc/cookbook/jwt-and-basic-auth"/dist/cookbook-jwt-and-basic-auth-*.tar.gz "doc/cookbook/db-sqlite-simple"/dist/cookbook-db-sqlite-simple-*.tar.gz "doc/cookbook/basic-auth"/dist/cookbook-basic-auth-*.tar.gz "doc/cookbook/https"/dist/cookbook-https-*.tar.gz "doc/cookbook/structuring-apis"/dist/cookbook-structuring-apis-*.tar.gz "doc/cookbook/using-custom-monad"/dist/cookbook-using-custom-monad-*.tar.gz "doc/cookbook/file-upload"/dist/cookbook-file-upload-*.tar.gz ${DISTDIR}/
- cd ${DISTDIR} || false
- find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
- "printf 'packages: servant-*/*.cabal servant-client-*/*.cabal servant-client-core-*/*.cabal servant-docs-*/*.cabal servant-foreign-*/*.cabal servant-server-*/*.cabal tutorial-*/*.cabal cookbook-db-postgres-pool-*/*.cabal cookbook-jwt-and-basic-auth-*/*.cabal cookbook-db-sqlite-simple-*/*.cabal cookbook-basic-auth-*/*.cabal cookbook-https-*/*.cabal cookbook-structuring-apis-*/*.cabal cookbook-using-custom-monad-*/*.cabal cookbook-file-upload-*/*.cabal\\n' > cabal.project"
- "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
- "echo 'allow-newer: servant-js:servant,servant-js:servant-foreign,servant-auth-server:http-types,servant-multipart:lens,servant-multipart:resourcet,servant-multipart:servant,servant-multipart:servant-server,servant-auth-server:servant-server' >> cabal.project"
- cat cabal.project
- echo -en 'travis_fold:end:unpack\\r'
- echo Building with tests and benchmarks... && echo -en 'travis_fold:start:build-everything\\r'
# build & run tests, build benchmarks
- cabal new-build -w ${HC} ${TEST} ${BENCH} ${CABALNEWBUILDOPTS} all
- echo -en 'travis_fold:end:build-everything\\r'
- if [ "x$TEST" = "x--enable-tests" ]; then cabal new-test -w ${HC} ${TEST} ${BENCH} ${CABALNEWBUILDOPTS} all; fi
- echo Haddock... && echo -en 'travis_fold:start:haddock\\r'
# haddock
- rm -rf ./dist-newstyle
- if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} ${CABALNEWBUILDOPTS} all; else echo "Skipping haddock generation";fi
- echo -en 'travis_fold:end:haddock\\r'
# REGENDATA ["--config=cabal.make-travis-yml","--output=.travis.yml","--max-backjumps=10000","cabal.project"]
# EOF

View file

@ -21,6 +21,7 @@ Or `nix`:
./scripts/generate-nix-files.sh # Get up-to-date shell.nix files ./scripts/generate-nix-files.sh # Get up-to-date shell.nix files
``` ```
To build the docs, see `doc/README.md`.
## General ## General
@ -34,8 +35,34 @@ Some things we like:
Though we aren't sticklers for style, the `.stylish-haskell.yaml` and `HLint.hs` Though we aren't sticklers for style, the `.stylish-haskell.yaml` and `HLint.hs`
files in the repository provide a good baseline for consistency. files in the repository provide a good baseline for consistency.
Please include a description of the changes in your PR in the `CHANGELOG.md` of **Important**: please do not modify the versions of the servant packages you are sending patches for.
the packages you've changed. And of course, write tests!
## Changelog entries
We experiment with using [changelog-d tool](https://github.com/phadej/changelog-d) to assemble changelogs.
You are not required to install it.
In each PR please add a file to `changelog.d` directory named after issue you are solving or the pull request itself (in a separate commit after you know the pull request number). For example
```cabal
synopsis: One sentence summary of the change.
prs: #1219
issues: #1028
description: {
A longer description. Small changes don't need this.
Bigger ones definitely do, for example we try to include migration hints
for breaking changes.
However if you don't know what to write, that's ok too.
By the way, the braces around are omitted when the file is parsed.
They can be used so the field doesn't need to be indented, which is handy
for prose.
}
```
## PR process ## PR process
@ -52,8 +79,10 @@ not been a timely response to a PR, you can ping the Maintainers group (with
We encourage people to experiment with new combinators and instances - it is We encourage people to experiment with new combinators and instances - it is
one of the most powerful ways of using `servant`, and a wonderful way of one of the most powerful ways of using `servant`, and a wonderful way of
getting to know it better. If you do write a new combinator, we would love to getting to know it better. If you do write a new combinator, we would love to
know about it! Either hop on #servant on freenode and let us know, or open an know about it! Either hop on
issue with the `news` tag (which we will close when we read it). [#haskell-servant on libera.chat](https://web.libera.chat/#haskell-servant) and
let us know, or open an issue with the `news` tag (which we will close when we
read it).
As for adding them to the main repo: maintaining combinators can be expensive, As for adding them to the main repo: maintaining combinators can be expensive,
since official combinators must have instances for all classes (and new classes since official combinators must have instances for all classes (and new classes
@ -76,7 +105,7 @@ the `news` label if you make a new package so we can know about it!
## Release policy ## Release policy
We are currently moving to a more aggresive release policy, so that you can get We are currently moving to a more aggressive release policy, so that you can get
what you contribute from Hackage fairly soon. However, note that prior to major what you contribute from Hackage fairly soon. However, note that prior to major
releases it may take some time in between releases. releases it may take some time in between releases.

View file

@ -4,37 +4,18 @@
## Getting Started ## Getting Started
We have a [tutorial](http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html) that We have a [tutorial](http://docs.servant.dev/en/stable/tutorial/index.html) that
introduces the core features of servant. After this article, you should be able introduces the core features of servant. After this article, you should be able
to write your first servant webservices, learning the rest from the haddocks' to write your first servant webservices, learning the rest from the haddocks'
examples. examples.
The central documentation can be found [here](http://haskell-servant.readthedocs.org/). The core documentation can be found [here](http://docs.servant.dev/).
Other blog posts, videos and slides can be found on the Other blog posts, videos and slides can be found on the
[website](http://haskell-servant.github.io/). [website](http://www.servant.dev/).
If you need help, drop by the IRC channel (#servant on freenode) or [mailing If you need help, drop by the IRC channel (#haskell-servant on libera.chat) or [mailing
list](https://groups.google.com/forum/#!forum/haskell-servant). list](https://groups.google.com/forum/#!forum/haskell-servant).
## Version history
This table lists the versions of some `servant-` libraries at the point of
release of `servant` package.
| | **0.10** | **0.11** | **0.12** |
| ------------------- | -------- |----------|----------|
| servant | 0.10 | 0.11 | 0.12 |
| servant-blaze | 0.7.1 | ? | ? |
| servant-cassava | 0.7 | ? | ? |
| servant-client | 0.10 | 0.11 | 0.12 |
| servant-docs | 0.10 | 0.11 | 0.11.1 |
| servant-foreign | 0.10 | 0.10.0.1 | 0.10.2 |
| servant-js | 0.9.1 | ? | ? |
| servant-lucid | 0.7.1 | ? | ? |
| servant-mock | 0.8.1.1 | ? | ? |
| servant-server | 0.10 | 0.11 | 0.12 |
| servant-swagger | 1.1.2.1 | ? | ? |
## Contributing ## Contributing
See `CONTRIBUTING.md` See `CONTRIBUTING.md`
@ -43,7 +24,7 @@ See `CONTRIBUTING.md`
- Update changelog and bump versions in `master` - Update changelog and bump versions in `master`
- `git log --oneline v0.12.. | grep 'Merge pull request'` is a good starting point (use correct previous release tag) - `git log --oneline v0.12.. | grep 'Merge pull request'` is a good starting point (use correct previous release tag)
- Create a release branch, e.g. `release-0.13`, and *protect it* from accidental force pushes. - Create a release branch, e.g. `release-0.13`
- Release branch is useful for backporting fixes from `master` - Release branch is useful for backporting fixes from `master`
- Smoke test in [`servant-universe`](https://github.com/phadej/servant-universe) - Smoke test in [`servant-universe`](https://github.com/phadej/servant-universe)
- `git submodule foreach git checkout master` and `git submodule foreach git pull` to get newest of everything. - `git submodule foreach git checkout master` and `git submodule foreach git pull` to get newest of everything.
@ -51,7 +32,7 @@ See `CONTRIBUTING.md`
- It's a good idea to separate these steps, as tests often pass, if they compile :) - It's a good idea to separate these steps, as tests often pass, if they compile :)
- See `cabal.project` to selectively `allow-newer` - See `cabal.project` to selectively `allow-newer`
- If some packages are broken, on your discretisation there are two options: - If some packages are broken, on your discretisation there are two options:
- Fix them and make PRs: it's good idea to test against older `servant` version too. - Fix them and make PRs: it's a good idea to test against older `servant` version too.
- Temporarily comment out broken package - Temporarily comment out broken package
- If you make a commit for `servant-universe`, you can use it as submodule in private projects to test even more - If you make a commit for `servant-universe`, you can use it as submodule in private projects to test even more
- When ripples are cleared out: - When ripples are cleared out:
@ -59,22 +40,32 @@ See `CONTRIBUTING.md`
- `git push --tags` - `git push --tags`
- `cabal sdist` and `cabal upload` - `cabal sdist` and `cabal upload`
## travis ## TechEmpower framework benchmarks
`.travis.yml` is generated using `make-travis-yml` tool, in We develop and maintain the servant TFB entry in https://github.com/haskell-servant/FrameworkBenchmarks/
[multi-ghc-travis](https://github.com/haskell-hvr/multi-ghc-travis) repository.
To regenerate the script use (*note:* atm you need to comment `doc/cookbook/` packages). To verify (i.e. compile and test that it works)
``` ```sh
runghc ~/Documents/other-haskell/multi-ghc-travis/make_travis_yml_2.hs regenerate ./tfb --mode verify --test servant servant-beam servant-psql-simple --type json plaintext db fortune
``` ```
In case Travis jobs fail due failing build of dependency, you can temporarily To compare with `warp`
add `constraints` to the `cabal.project`, and regenerate the `.travis.yml`.
For example, the following will disallow single `troublemaker-13.37` package version:
```sh
./tfb --mode benchmark --test warp servant servant-beam servant-psql-simple --type json plaintext db fortune
``` ```
constraints:
troublemaker <13.37 && > 13.37 To compare with `reitit` (Clojure framework)
```sh
./tfb --mode benchmark --test reitit reitit-async reitit-jdbc servant servant-beam servant-psql-simple --type json plaintext db fortune
``` ```
You can see the visualised results at https://www.techempower.com/benchmarks/#section=test
## Nix
A developer shell.nix file is provided in the `nix` directory
See [nix/README.md](nix/README.md)

14
cabal.ghcjs.project Normal file
View file

@ -0,0 +1,14 @@
-- Using https://launchpad.net/~hvr/+archive/ubuntu/ghcjs
packages:
servant/
servant-client/
servant-client-core/
-- we need to tell cabal we are using GHCJS
compiler: ghcjs
tests: True
-- Constraints so that reflex-platform provided packages are selected.
constraints: attoparsec == 0.13.2.2
constraints: hashable == 1.3.0.0

View file

@ -1,15 +0,0 @@
folds: all-but-test
branches: master release-0.12
-- We have inplace packages (servant-js) so we skip installing dependencies in a separate step
install-dependencies-step: False
-- this speed-ups the build a little, but we have to check these for release
no-tests-no-benchmarks: False
build-with-installed-step: False
-- Don't run cabal check, as cookbook examples won't pass it
cabal-check: False
-- ghc-options: -j2
jobs: :2

View file

@ -1,23 +1,54 @@
packages: servant/ packages:
servant/
servant-auth/servant-auth
servant-auth/servant-auth-client
servant-auth/servant-auth-docs
servant-auth/servant-auth-server
servant-auth/servant-auth-swagger
servant-client/ servant-client/
servant-client-core/ servant-client-core/
servant-http-streams/
servant-docs/ servant-docs/
servant-foreign/ servant-foreign/
servant-server/ servant-server/
servant-swagger/
doc/tutorial/ doc/tutorial/
doc/cookbook/*/*.cabal
allow-newer: -- servant streaming
servant-js:servant, packages:
servant-js:servant-foreign, servant-machines/
servant-auth-server:http-types, servant-conduit/
servant-multipart:lens, servant-pipes/
servant-multipart:resourcet,
servant-multipart:servant,
servant-multipart:servant-server,
servant-auth-server:servant-server
constraints: -- servant GHCJS
-- see https://github.com/haskell-infra/hackage-trustees/issues/119 -- packages:
foundation >=0.0.14, -- servant-jsaddle/
memory <0.14.12 || >0.14.12
-- Cookbooks
packages:
doc/cookbook/basic-auth
doc/cookbook/curl-mock
doc/cookbook/custom-errors
doc/cookbook/basic-streaming
doc/cookbook/db-postgres-pool
doc/cookbook/db-sqlite-simple
doc/cookbook/file-upload
doc/cookbook/generic
doc/cookbook/hoist-server-with-context
doc/cookbook/https
doc/cookbook/jwt-and-basic-auth
doc/cookbook/pagination
-- doc/cookbook/sentry
-- Commented out because servant-quickcheck currently doesn't build.
-- doc/cookbook/testing
doc/cookbook/uverb
doc/cookbook/structuring-apis
doc/cookbook/using-custom-monad
doc/cookbook/using-free-client
-- doc/cookbook/open-id-connect
doc/cookbook/managed-resource
tests: True
optimization: False
-- reorder-goals: True

9
changelog.d/1432 Normal file
View file

@ -0,0 +1,9 @@
synopsis: Fixes encoding of URL parameters in servant-client
prs: #1432
issues: #1418
description: {
Some applications use query parameters to pass arbitrary (non-unicode) binary
data. This change modifies how servant-client handles query parameters, so
that application developers can use `ToHttpApiData` to marshal binary data into
query parameters.
}

11
changelog.d/1469 Normal file
View file

@ -0,0 +1,11 @@
synopsis: Derive HasClient good response status from Verb status
prs: #1469
description: {
`HasClient` instances for the `Verb` datatype use `runRequest` in
`clientWithRoute` definitions.
This means that a request performed with `runClientM` will be successful if and
only if the endpoint specify a response status code >=200 and <300.
This change replaces `runRequest` with `runRequestAcceptStatus` in `Verb`
instances for the `HasClient` class, deriving the good response status from
the `Verb` status.
}

9
changelog.d/1477 Normal file
View file

@ -0,0 +1,9 @@
synopsis: Enable FlexibleContexts in Servant.API.ContentTypes
prs: #1477
description: {
Starting with GHC 9.2, UndecidableInstances no longer implies FlexibleContexts.
Add this extension where it's needed to make compilation succeed.
}

10
changelog.d/1529 Normal file
View file

@ -0,0 +1,10 @@
synopsis: Fix performRequest in servant-client-ghcjs
prs: #1529
description: {
performRequest function in servant-client-ghcjs was not compatible with the
latest RunClient typeclass. Added the acceptStatus parameter and fixed the
functionality to match what servant-client provides.
}

81
changelog.d/1556 Normal file
View file

@ -0,0 +1,81 @@
synopsis: Display capture hints in router layout
prs: #1556
description: {
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.
Example:
For the following API
```haskell
type API =
"a" :> "d" :> Get '[JSON] NoContent
:<|> "b" :> Capture "x" Int :> Get '[JSON] Bool
:<|> "a" :> "e" :> Get '[JSON] Int
```
we previously got the following output:
```
/
├─ a/
│ ├─ d/
│ │ └─•
│ └─ e/
│ └─•
└─ b/
└─ <capture>/
├─•
└─•
```
now we get:
```
/
├─ a/
│ ├─ d/
│ │ └─•
│ └─ e/
│ └─•
└─ b/
└─ <x::Int>/
├─•
└─•
```
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.
CaptureHint values are then used in routerLayout, to display the name and type of captured values, instead of just `<capture>` previously.
N.B.:
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.
This PR also introduces Spec tests for the routerLayout function.
Warning:
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`.
In those cases, the fix is easy: it suffices to add `Typeable a` to the left-hand side of the quantification constraint.
For instance, the following code will no longer compile:
```haskell
type MyAPI a = Capture "foo" a :> Get '[JSON] ()
myServer :: forall a. Server (MyAPI a)
myServer = const $ return ()
myApi :: forall a. Proxy (MyAPI a)
myApi = Proxy
app :: forall a. (FromHttpApiData a) => Application
app = serve (myApi @a) (myServer @a)
```
Indeed, `app` should be replaced with:
```haskell
app :: forall a. (FromHttpApiData a, Typeable a) => Application
app = serve (myApi @a) (myServer @a)
```
}

13
changelog.d/1569 Normal file
View file

@ -0,0 +1,13 @@
synopsis: Encode captures using toEncodedUrlPiece
prs: #1569
issues: #1511
description: {
The `servant-client` library now makes direct use of `toEncodedUrlPiece` from `ToHttpApiData`
to encode captured values when building the request path. It gives user freedom to implement
URL-encoding however they need.
Previous behavior was to use `toUrlPiece` and URL-encode its output using `toEncodedUrlPiece`
from the `Text` instance of `ToHttpApiData`. The issue with this approach is that
`ToHttpApiData Text` is overly zealous and also encodes characters, such as `*`, which are perfectly valid in a URL.
}

2
changelog.d/1573 Normal file
View file

@ -0,0 +1,2 @@
synopsis: Add API docs for ServerT
prs: #1573

12
changelog.d/1580 Normal file
View file

@ -0,0 +1,12 @@
synopsis: Allow IO in validationKeys
prs: #1580
issues: #1579
description: {
Currently validationKeys are a fixed JWKSet. This does not work with OIDC
providers such as AWS Cognito or Okta, which regularly fetching jwks_uri to
discover new and expired keys.
This change alters the type of validationKeys from JWKSet to IO JWKSet.
}

2
changelog.d/1589 Normal file
View file

@ -0,0 +1,2 @@
synopsis: Only include question mark for nonempty query strings
prs: 1589

2
changelog.d/1595 Normal file
View file

@ -0,0 +1,2 @@
synopsis: Run ClientEnv's makeClientRequest in IO.
prs: #1595

10
changelog.d/1606 Normal file
View file

@ -0,0 +1,10 @@
synopsis: Handle Cookies correctly for RunStreamingClient
prs: #1606
issues: #1605
description: {
Makes performWithStreamingRequest take into consideration the
CookieJar, which it previously didn't.
}

2
changelog.d/1638 Normal file
View file

@ -0,0 +1,2 @@
synopsis: Add Functor instance to AuthHandler.
prs: #1638

8
changelog.d/1649 Normal file
View file

@ -0,0 +1,8 @@
synopsis: Add HasStatus instance for Headers (that defers StatusOf to underlying value)
prs: #1649
description: {
Adds a new HasStatus (Headers hs a) instance (StatusOf (Headers hs a) = StatusOf a)
}

2
changelog.d/config Normal file
View file

@ -0,0 +1,2 @@
organization: haskell-servant
repository: servant

View file

@ -0,0 +1,16 @@
synopsis: Add sample cURL requests to generated documentation
prs: #1401
description: {
Add sample cURL requests to generated documentation.
Those supplying changes to the Request `header` field manually using
lenses will need to add a sample bytestring value.
`headers <>~ ["unicorn"]`
becomes
`headers <>~ [("unicorn", "sample value")]`
}

38
default.nix Normal file
View file

@ -0,0 +1,38 @@
with (builtins.fromJSON (builtins.readFile ./nix/nixpkgs.json));
{
pkgs ? import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
}) {}
, compiler ? "ghc883"
}:
let
overrides = self: super: {
servant = self.callCabal2nix "servant" ./servant {};
servant-docs = self.callCabal2nix "servant-docs" ./servant-docs {};
servant-pipes = self.callCabal2nix "servant-pipes" ./servant-pipes {};
servant-server = self.callCabal2nix "servant-server" ./servant-server {};
servant-client = self.callCabal2nix "servant-client" ./servant-client {};
servant-foreign = self.callCabal2nix "servant-foreign" ./servant-foreign {};
servant-conduit = self.callCabal2nix "servant-conduit" ./servant-conduit {};
servant-machines = self.callCabal2nix "servant-machines" ./servant-machines {};
servant-client-core = self.callCabal2nix "servant-client-core" ./servant-client-core {};
servant-http-streams = self.callCabal2nix "servant-http-streams" ./servant-http-streams {};
};
hPkgs = pkgs.haskell.packages.${compiler}.override { inherit overrides; };
in
with hPkgs;
{
inherit
servant
servant-client
servant-client-core
servant-conduit
servant-docs
servant-foreign
servant-http-streams
servant-machines
servant-pipes
servant-server;
}

View file

@ -1,216 +1,22 @@
# Makefile for Sphinx documentation # Minimal makefile for Sphinx documentation
# #
# You can set these variables from the command line. # You can set these variables from the command line.
SPHINXOPTS = SPHINXOPTS =
SPHINXBUILD = sphinx-build SPHINXBUILD = sphinx-build
PAPER = SPHINXPROJ = Servant
SOURCEDIR = .
BUILDDIR = _build BUILDDIR = _build
# User-friendly check for sphinx-build # Put it first so that "make" without argument is like "make help".
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
help: help:
@echo "Please use \`make <target>' where <target> is one of" @if [ ! -d venv ]; then echo "WARNING: There is no venv directory, did you forget to 'virtualenv venv'. Check README.md."; fi
@echo " html to make standalone HTML files" @if [ ! "z$$(which $(SPHINXBUILD))" = "z$$(pwd)/venv/bin/sphinx-build" ]; then echo "WARNING: Did you forgot to 'source venv/bin/activate'"; fi
@echo " dirhtml to make HTML files named index.html in directories" @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
.PHONY: clean .PHONY: help Makefile
clean:
rm -rf $(BUILDDIR)/*
.PHONY: html # Catch-all target: route all unknown targets to Sphinx using the new
html: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html %: Makefile
@echo @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/generics-eot.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/generics-eot.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/generics-eot"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/generics-eot"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

8
doc/README.md Normal file
View file

@ -0,0 +1,8 @@
To build the docs locally:
$ virtualenv venv
$ . ./venv/bin/activate
$ pip install -r requirements.txt
$ make html
Docs will be built in _build/html/index.html .

View file

@ -1,8 +0,0 @@
To build the docs locally:
$ virtualenv venv
$ . ./venv/bin/activate
$ pip install -r requirements.txt
$ make html
Docs will be built in _build/html/index.html .

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# servant documentation build configuration file, created by # Servant documentation build configuration file, created by
# sphinx-quickstart on Mon Nov 23 13:24:36 2015. # sphinx-quickstart on Fri Jul 6 11:38:51 2018.
# #
# This file is execfile()d with the current directory set to its # This file is execfile()d with the current directory set to its
# containing dir. # containing dir.
@ -12,20 +12,21 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys
import os
import shlex
from recommonmark.parser import CommonMarkParser
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.')) #
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
from recommonmark.parser import CommonMarkParser
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0' #
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@ -37,17 +38,15 @@ templates_path = ['_templates']
# The suffix(es) of source filenames. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string: # You can specify multiple suffix as a list of string:
source_suffix = ['.md', '.rst', '.lhs'] #
source_suffix = ['.rst', '.md', '.lhs']
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = u'servant' project = u'Servant'
copyright = u'2016, Servant Contributors' copyright = u'2022, Servant Contributors'
author = u'Servant Contributors' author = u'Servant Contributors'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
@ -55,9 +54,9 @@ author = u'Servant Contributors'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
# version = 'latest' # version = u'0.14'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
# release = 'latest' # release = u'0.14'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -66,45 +65,14 @@ author = u'Servant Contributors'
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = None language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
exclude_patterns = ['_build', 'venv'] # This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = 'sphinx'
def setup(app):
from sphinx.highlighting import lexers
from pygments.lexers import HaskellLexer
lexers['haskell ignore'] = HaskellLexer(stripnl=False)
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing. # If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False todo_include_todos = False
@ -113,157 +81,77 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme' html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} #
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or # Custom sidebar templates, must be a dictionary that maps document names
# .htaccess) here, relative to this directory. These files are copied # to template names.
# directly to the root of the documentation. #
#html_extra_path = [] # This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'**': [
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
]
}
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to # -- Options for HTMLHelp output ------------------------------------------
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'servantdoc' htmlhelp_basename = 'Servantdoc'
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt', #
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#'preamble': '', #
# 'preamble': '',
# Latex figure (float) alignment # Latex figure (float) alignment
#'figure_align': 'htbp', #
# 'figure_align': 'htbp',
} }
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
(master_doc, 'servant.tex', u'servant Documentation', (master_doc, 'Servant.tex', u'Servant Documentation',
u'Servant Contributors', 'manual'), u'Servant Contributors', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------- # -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
(master_doc, 'servant', u'servant Documentation', (master_doc, 'servant', u'Servant Documentation',
[author], 1) [author], 1)
] ]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------- # -- Options for Texinfo output -------------------------------------------
@ -271,24 +159,13 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
(master_doc, 'servant', u'servant Documentation', (master_doc, 'Servant', u'Servant Documentation',
author, 'servant', 'One line description of project.', author, 'Servant', 'One line description of project.',
'Miscellaneous'), 'Miscellaneous'),
] ]
# Documents to append as an appendix to all manuals. # -- Markdown -------------------------------------------------------------
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
source_parsers = { source_parsers = {
'.md': CommonMarkParser,
'.lhs': CommonMarkParser, '.lhs': CommonMarkParser,
} }

View file

@ -1,14 +1,14 @@
cabal-version: 2.2
name: cookbook-basic-auth name: cookbook-basic-auth
version: 0.1 version: 0.1
synopsis: Basic Authentication cookbook example synopsis: Basic Authentication cookbook example
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-basic-auth executable cookbook-basic-auth
main-is: BasicAuth.lhs main-is: BasicAuth.lhs

View file

@ -0,0 +1,129 @@
# Streaming out-of-the-box
In other words, without streaming libraries.
## Introduction
- Servant supports streaming
- Some basic usage doesn't require usage of streaming libraries,
like `conduit`, `pipes`, `machines` or `streaming`.
We have bindings for them though.
- Similar example is bundled with each of our streaming library interop packages (see
[servant-pipes](https://github.com/haskell-servant/servant/blob/master/servant-pipes/example/Main.hs),
[servant-conduit](https://github.com/haskell-servant/servant/blob/master/servant-conduit/example/Main.hs) and
[servant-machines](https://github.com/haskell-servant/servant/blob/master/servant-machines/example/Main.hs))
- `SourceT` doesn't have *Prelude* with handy combinators, so we have to write
things ourselves. (Note to self: `mapM` and `foldM` would be handy to have).
## Code
```haskell
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}
module Main (main) where
import Control.Concurrent
(threadDelay)
import Control.Monad.IO.Class
(MonadIO (..))
import qualified Data.ByteString as BS
import Data.Maybe
(fromMaybe)
import Network.HTTP.Client
(defaultManagerSettings, newManager)
import Network.Wai
(Application)
import System.Environment
(getArgs, lookupEnv)
import Text.Read
(readMaybe)
import Servant
import Servant.Client.Streaming
import qualified Servant.Types.SourceT as S
import qualified Network.Wai.Handler.Warp as Warp
type FastAPI = "get" :> Capture "num" Int :> StreamGet NewlineFraming JSON (SourceIO Int)
type API = FastAPI
:<|> "slow" :> Capture "num" Int :> StreamGet NewlineFraming JSON (SourceIO Int)
-- monad can be ResourceT IO too.
:<|> "readme" :> StreamGet NoFraming OctetStream (SourceIO BS.ByteString)
-- we can have streaming request body
:<|> "proxy"
:> StreamBody NoFraming OctetStream (SourceIO BS.ByteString)
:> StreamPost NoFraming OctetStream (SourceIO BS.ByteString)
api :: Proxy API
api = Proxy
server :: Server API
server = fast :<|> slow :<|> readme :<|> proxy where
fast n = liftIO $ do
putStrLn $ "/get/" ++ show n
return $ fastSource n
slow n = liftIO $ do
putStrLn $ "/slow/" ++ show n
return $ slowSource n
readme = liftIO $ do
putStrLn "/proxy"
return (S.readFile "README.md")
proxy c = liftIO $ do
putStrLn "/proxy"
return c
-- for some reason unfold leaks?
fastSource = S.fromStepT . mk where
mk m
| m < 0 = S.Stop
| otherwise = S.Yield m (mk (m - 1))
slowSource m = S.mapStepT delay (fastSource m) where
delay S.Stop = S.Stop
delay (S.Error err) = S.Error err
delay (S.Skip s) = S.Skip (delay s)
delay (S.Effect ms) = S.Effect (fmap delay ms)
delay (S.Yield x s) = S.Effect $
S.Yield x (delay s) <$ threadDelay 1000000
app :: Application
app = serve api server
cli :: Client ClientM FastAPI
cli :<|> _ :<|> _ :<|> _ = client api
main :: IO ()
main = do
args <- getArgs
case args of
("server":_) -> do
putStrLn "Starting cookbook-basic-streaming at http://localhost:8000"
port <- fromMaybe 8000 . (>>= readMaybe) <$> lookupEnv "PORT"
Warp.run port app
("client":ns:_) -> do
n <- maybe (fail $ "not a number: " ++ ns) pure $ readMaybe ns
mgr <- newManager defaultManagerSettings
burl <- parseBaseUrl "http://localhost:8000/"
withClientM (cli n) (mkClientEnv mgr burl) $ \me -> case me of
Left err -> print err
Right src -> do
x <- S.unSourceT src (go (0 :: Int))
print x
where
go !acc S.Stop = return acc
go !acc (S.Error err) = print err >> return acc
go !acc (S.Skip s) = go acc s
go !acc (S.Effect ms) = ms >>= go acc
go !acc (S.Yield _ s) = go (acc + 1) s
_ -> do
putStrLn "Try:"
putStrLn "cabal new-run cookbook-basic-streaming server"
putStrLn "cabal new-run cookbook-basic-streaming client 10"
putStrLn "time curl -H 'Accept: application/json' localhost:8000/slow/5"
```

View file

@ -0,0 +1,28 @@
cabal-version: 2.2
name: cookbook-basic-streaming
version: 2.1
synopsis: Streaming in servant without streaming libs
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-basic-streaming
main-is: Streaming.lhs
build-tool-depends: markdown-unlit:markdown-unlit
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit -threaded -rtsopts
hs-source-dirs: .
build-depends: base >= 4.8 && <5
, aeson
, bytestring
, servant
, servant-server
, servant-client
, http-client
, wai
, warp

View file

@ -1,12 +1,19 @@
packages: packages:
basic-auth/ basic-auth/
curl-mock/
db-mysql-basics/
db-sqlite-simple/ db-sqlite-simple/
db-postgres-pool/ db-postgres-pool/
using-custom-monad/ using-custom-monad/
jwt-and-basic-auth/ jwt-and-basic-auth/
hoist-server-with-context/
file-upload/ file-upload/
structuring-apis/ structuring-apis/
https/ https/
pagination/
sentry/
testing/
open-id-connect/
../../servant ../../servant
../../servant-server ../../servant-server
../../servant-client-core ../../servant-client-core

View file

View file

@ -0,0 +1,205 @@
# Generating mock curl calls
In this example we will generate curl requests with mock post data from a servant API.
This may be useful for testing and development purposes.
Especially post requests with a request body are tedious to send manually.
Also, we will learn how to use the servant-foreign library to generate stuff from servant APIs.
Language extensions and imports:
``` haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
import Control.Lens ((^.))
import Data.Aeson
import Data.Aeson.Text
import Data.Proxy (Proxy (Proxy))
import Data.Text (Text)
import Data.Text.Encoding (decodeUtf8)
import qualified Data.Text.IO as T.IO
import qualified Data.Text.Lazy as LazyT
import GHC.Generics
import Servant ((:<|>), (:>), Get, JSON,
Post, ReqBody)
import Servant.Foreign (Foreign, GenerateList,
HasForeign, HasForeignType, Req,
Segment, SegmentType (Cap, Static),
argName, listFromAPI, path,
reqBody, reqMethod, reqUrl, typeFor,
unPathSegment, unSegment,)
import Test.QuickCheck.Arbitrary
import Test.QuickCheck.Arbitrary.Generic
import Test.QuickCheck.Gen (generate)
import qualified Data.Text as T
```
Let's define our API:
``` haskell
type UserAPI = "users" :> Get '[JSON] [User]
:<|> "new" :> "user" :> ReqBody '[JSON] User :> Post '[JSON] ()
data User = User
{ name :: String
, age :: Int
, email :: String
} deriving (Eq, Show, Generic)
instance Arbitrary User where
arbitrary = genericArbitrary
shrink = genericShrink
instance ToJSON User
instance FromJSON User
```
Notice the `Arbitrary User` instance which we will later need to create mock data.
Also, the obligatory servant boilerplate:
``` haskell
api :: Proxy UserAPI
api = Proxy
```
## servant-foreign and the HasForeignType Class
Servant-foreign allows us to look into the API we designed.
The entry point is `listFromAPI` which takes three types and returns a list of endpoints:
``` haskell ignore
listFromAPI :: (HasForeign lang ftype api, GenerateList ftype (Foreign ftype api)) => Proxy lang -> Proxy ftype -> Proxy api -> [Req ftype]
```
This looks a bit confusing...
[Here](https://hackage.haskell.org/package/servant-foreign/docs/Servant-Foreign.html#t:HasForeign) is the documentation for the `HasForeign` typeclass.
We will not go into details here, but this allows us to create a value of type `ftype` for any type `a` in our API.
In our case we want to create a mock of every type `a`.
We create a new datatype that holds our mocked value. Well, not the mocked value itself. To mock it we need IO (random). So the promise of a mocked value after some IO is performed:
``` haskell
data NoLang
data Mocked = Mocked (IO Text)
```
Now, we create an instance of `HasForeignType` for `NoLang` and `Mocked` for every `a` that implements `ToJSON` and `Arbitrary`:
``` haskell
instance (ToJSON a, Arbitrary a) => HasForeignType NoLang Mocked a where
typeFor _ _ _ =
Mocked (genText (Proxy :: Proxy a))
```
What does `genText` do? It generates an arbitrary value of type `a` and encodes it as text. (And does some lazy to non-lazy text transformation we do not care about):
``` haskell
genText :: (ToJSON a, Arbitrary a) => Proxy a -> IO Text
genText p =
fmap (\v -> LazyT.toStrict $ encodeToLazyText v) (genArb p)
genArb :: Arbitrary a => Proxy a -> IO a
genArb _ =
generate arbitrary
```
### Generating curl calls for every endpoint
Everything is prepared now and we can start generating some curl calls.
``` haskell
generateCurl :: (GenerateList Mocked (Foreign Mocked api), HasForeign NoLang Mocked api)
=> Proxy api
-> Text
-> IO Text
generateCurl p host =
fmap T.unlines body
where
body = mapM (generateEndpoint host)
$ listFromAPI (Proxy :: Proxy NoLang) (Proxy :: Proxy Mocked) p
```
First, `listFromAPI` gives us a list of `Req Mocked`. Each `Req` describes one endpoint from the API type.
We generate a curl call for each of them using the following helper.
``` haskell
generateEndpoint :: Text -> Req Mocked -> IO Text
generateEndpoint host req =
case maybeBody of
Just body ->
body >>= \b -> return $ T.intercalate " " [ "curl", "-X", method, "-d", "'" <> b <> "'"
, "-H 'Content-Type: application/json'", host <> "/" <> url ]
Nothing ->
return $ T.intercalate " " [ "curl", "-X", method, host <> "/" <> url ]
where
method = decodeUtf8 $ req ^. reqMethod
url = T.intercalate "/" $ map segment (req ^. reqUrl . path)
maybeBody = fmap (\(Mocked io) -> io) (req ^. reqBody)
```
`servant-foreign` offers a multitude of lenses to be used with `Req`-values.
`reqMethod` gives us a straigthforward `Network.HTTP.Types.Method`, `reqUrl` the url part and so on.
Just take a look at [the docs](https://hackage.haskell.org/package/servant-foreign/docs/Servant-Foreign.html).
But how do we get our mocked json string? This seems to be a bit to short to be true:
``` haskell ignore
maybeBody = fmap (\(Mocked io) -> io) (req ^. reqBody)
```
But it is that simple!
The docs say `reqBody` gives us a `Maybe f`. What is `f`, you ask? As defined in `generateCurl`, `f` is `Mocked` and contains a `IO Text`. How is this `Mocked` value created? The `HasForeignType::typeFor` does it!
Of course only if the endpoint has a request body.
Some (incomplete) code for url segments:
``` haskell
segment :: Segment Mocked -> Text
segment seg =
case unSegment seg of
Static p ->
unPathSegment p
Cap arg ->
-- Left as exercise for the reader: Mock args in the url
unPathSegment $ arg ^. argName
```
And now, lets hook it all up in our main function:
``` haskell
main :: IO ()
main =
generateCurl api "localhost:8081" >>= T.IO.putStrLn
```
Done:
``` curl
curl -X GET localhost:8081/users
curl -X POST -d '{"email":"wV򝣀_b򆎘:z񁊞򲙲!(3DM V","age":10,"name":"=|W"}' -H 'Content-Type: application/json' localhost:8081/new/user
```
This is of course no complete curl call mock generator, many things including path arguments are missing.
But it correctly generates mock calls for simple POST requests.
Also, we now know how to use `HasForeignType` and `listFromAPI` to generate anything we want.

View file

@ -0,0 +1,29 @@
cabal-version: 2.2
name: cookbook-curl-mock
version: 0.1
synopsis: Generate curl mock requests cookbook example
homepage: http://docs.servant.dev
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbock-curl-mock
if impl(ghc >= 9.2)
-- generic-arbitrary is incompatible
buildable: False
main-is: CurlMock.lhs
build-depends: base == 4.*
, aeson
, lens
, text
, servant
, servant-server
, servant-foreign
, QuickCheck
, generic-arbitrary
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit

View file

@ -0,0 +1,189 @@
# Customizing errors from Servant
Servant handles a lot of parsing and validation of the input request. When it can't parse something: query
parameters, URL parts or request body, it will return appropriate HTTP codes like 400 Bad Request.
These responses will contain the error message in their body without any formatting. However, it is often
desirable to be able to provide custom formatting for these error messages, for example, to wrap them in JSON.
Recently Servant got a way to add such formatting. This Cookbook chapter demonstrates how to use it.
Extensions and imports:
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
import Data.Aeson
import Data.Proxy
import Data.Text
import GHC.Generics
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import Data.String.Conversions
(cs)
import Servant.API.ContentTypes
```
The API (from `greet.hs` example in Servant sources):
```haskell
-- | A greet message data type
newtype Greet = Greet { _msg :: Text }
deriving (Generic, Show)
instance FromJSON Greet
instance ToJSON Greet
-- API specification
type TestApi =
-- GET /hello/:name?capital={true, false} returns a Greet as JSON
"hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON] Greet
-- POST /greet with a Greet as JSON in the request body,
-- returns a Greet as JSON
:<|> "greet" :> ReqBody '[JSON] Greet :> Post '[JSON] Greet
-- DELETE /greet/:greetid
:<|> "greet" :> Capture "greetid" Text :> Delete '[JSON] NoContent
testApi :: Proxy TestApi
testApi = Proxy
-- Server-side handlers.
--
-- There's one handler per endpoint, which, just like in the type
-- that represents the API, are glued together using :<|>.
--
-- Each handler runs in the 'Handler' monad.
server :: Server TestApi
server = helloH :<|> postGreetH :<|> deleteGreetH
where helloH name Nothing = helloH name (Just False)
helloH name (Just False) = return . Greet $ "Hello, " <> name
helloH name (Just True) = return . Greet . toUpper $ "Hello, " <> name
postGreetH greet = return greet
deleteGreetH _ = return NoContent
```
## Error formatters
`servant-server` provides an `ErrorFormatter` type to specify how the error message will be
formatted. A formatter is just a function accepting three parameters:
- `TypeRep` from `Data.Typeable`: this is a runtime representation of the type of the combinator
(like `Capture` or `ReqBody`) that generated the error. It can be used to display its name (with
`show`) or even dynamically dispatch on the combinator type. See the docs for `Data.Typeable` and
`Type.Reflection` modules.
- `Request`: full information for the request that led to the error.
- `String`: specific error message from the combinator.
The formatter is expected to produce a `ServerError` which will be returned from the handler.
Additionally, there is `NotFoundErrorFormatter`, which accepts only `Request` and can customize the
error in case when no route can be matched (HTTP 404).
Let's make two formatters. First one will wrap our error in a JSON:
```json
{
"error": "ERROR MESSAGE",
"combinator": "NAME OF THE COMBINATOR"
}
```
Additionally, this formatter will examine the `Accept` header of the request and generate JSON
message only if client can accept it.
```haskell
customFormatter :: ErrorFormatter
customFormatter tr req err =
let
-- aeson Value which will be sent to the client
value = object ["combinator" .= show tr, "error" .= err]
-- Accept header of the request
accH = getAcceptHeader req
in
-- handleAcceptH is Servant's function that checks whether the client can accept a
-- certain message type.
-- In this case we call it with "Proxy '[JSON]" argument, meaning that we want to return a JSON.
case handleAcceptH (Proxy :: Proxy '[JSON]) accH value of
-- If client can't handle JSON, we just return the body the old way
Nothing -> err400 { errBody = cs err }
-- Otherwise, we return the JSON formatted body and set the "Content-Type" header.
Just (ctypeH, body) -> err400
{ errBody = body
, errHeaders = [("Content-Type", cs ctypeH)]
}
notFoundFormatter :: NotFoundErrorFormatter
notFoundFormatter req =
err404 { errBody = cs $ "Not found path: " <> rawPathInfo req }
```
If you don't need to react to the `Accept` header, you can just unconditionally return the JSON like
this (with `encode` from `Data.Aeson`):
```
err400
{ errBody = encode body
, errHeaders = [("Content-Type", "application/json")]
}
```
## Passing formatters to Servant
Servant uses the Context to configure formatters. You only need to add a value of type
`ErrorFormatters` to your context. This is a record with the following fields:
- `bodyParserErrorFormatter :: ErrorFormatter`
- `urlParseErrorFormatter :: ErrorFormatter`
- `headerParseErrorFormatter :: ErrorFormatter`
- `notFoundErrorFormatter :: NotFoundErrorFormatter`
Default formatters are exported as `defaultErrorFormatters`, so you can use record update syntax to
set the only ones you need:
```haskell
customFormatters :: ErrorFormatters
customFormatters = defaultErrorFormatters
{ bodyParserErrorFormatter = customFormatter
, notFoundErrorFormatter = notFoundFormatter
}
```
And at last, use `serveWithContext` to run your server as usual:
```haskell
app :: Application
app = serveWithContext testApi (customFormatters :. EmptyContext) server
main :: IO ()
main = run 8000 app
```
Now if we try to request something with a wrong body, we will get a nice error:
```
$ http -j POST localhost:8000/greet 'foo=bar'
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=utf-8
Date: Fri, 17 Jul 2020 13:34:18 GMT
Server: Warp/3.3.12
Transfer-Encoding: chunked
{
"combinator": "ReqBody'",
"error": "Error in $: parsing Main.Greet(Greet) failed, key \"_msg\" not found"
}
```
Notice the `Content-Type` header set by our combinator.

View file

@ -0,0 +1,25 @@
cabal-version: 2.2
name: cookbook-custom-errors
version: 0.1
synopsis: Return custom error messages from combinators
homepage: http://docs.servant.dev
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-custom-errors
main-is: CustomErrors.lhs
build-depends: base == 4.*
, aeson
, servant
, servant-server
, string-conversions
, text
, wai
, warp
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit

View file

@ -0,0 +1,236 @@
# Overview
This doc will walk through a single-module implementation of a servant API connecting to a MySQL database. It will also include some basic CRUD operations.
Once you can wrap your head around this implementation, understanding more complex features like resource pools would be beneficial next steps.
The only *prerequisite* is that you have a MySQL database open on port 3306 of your machine. Docker is an easy way to manage this.
## Setup
- The mysql database should be up and running on 127.0.0.1:3306
- Our API will be exposed on localhost:8080
## REST actions available
*Get all people*
```
/people GET
```
*Get person by ID*
```
/people/:id GET
```
*Insert a new person*
```
/people POST
{
"name": "NewName",
"age": 24
}
```
*Delete a person*
```
/people/:id DELETE
```
## Other notes
At the time of writing this issue may occur when building your project:
```
setup: Missing dependencies on foreign libraries:
* Missing (or bad) C libraries: ssl, crypto
```
If using stack, this can be fixed by adding the following lines to your `stack.yaml`:
```
extra-include-dirs:
- /usr/local/opt/openssl/include
extra-lib-dirs:
- /usr/local/opt/openssl/lib
```
Or for cabal, running your builds with these configurations passed as options.
## Implementation: Main.hs
Let's jump in:
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Lib where
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (NoLoggingT (..))
import Control.Monad.Trans.Reader (runReaderT)
import Control.Monad.Trans.Resource (ResourceT, runResourceT)
import Data.Aeson as JSON
import Data.Int (Int64 (..))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Database.Persist
import Database.Persist.MySQL (ConnectInfo (..),
SqlBackend (..),
defaultConnectInfo, fromSqlKey, runMigration,
runSqlPool, toSqlKey, withMySQLConn)
import Database.Persist.Sql (SqlPersistT, runSqlConn)
import Database.Persist.TH (mkMigrate, mkPersist,
persistLowerCase, share,
sqlSettings)
import Database.Persist.Types (PersistValue(PersistInt64))
import Servant (Handler, throwError)
import GHC.Generics
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import Servant.API
import System.Environment (getArgs)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person json
Id Int Primary Unique
name Text
age Text
deriving Eq Show Generic
|]
type Api =
"person" :> Get '[JSON] [Person]
:<|> "person" :> Capture "id" Int :> Get '[JSON] Person
:<|> "person" :> Capture "id" Int :> Delete '[JSON] ()
:<|> "person" :> ReqBody '[JSON] Person :> Post '[JSON] Person
apiProxy :: Proxy Api
apiProxy = Proxy
app :: Application
app = serve apiProxy server
-- Run a database operation, and lift the result into a Handler.
-- This minimises usage of IO operations in other functions
runDB :: SqlPersistT (ResourceT (NoLoggingT IO)) a -> Handler a
runDB a = liftIO $ runNoLoggingT $ runResourceT $ withMySQLConn connInfo $ runSqlConn a
-- Change these out to suit your local setup
connInfo :: ConnectInfo
connInfo = defaultConnectInfo { connectHost = "127.0.0.1", connectUser = "root", connectPassword = "abcd", connectDatabase = "test-database" }
doMigration :: IO ()
doMigration = runNoLoggingT $ runResourceT $ withMySQLConn connInfo $ runReaderT $ runMigration migrateAll
server :: Server Api
server =
personGET :<|>
personGETById :<|>
personDELETE :<|>
personPOST
where
personGET = selectPersons
personGETById id = selectPersonById id
personDELETE id = deletePerson id
personPOST personJson = createPerson personJson
selectPersons :: Handler [Person]
selectPersons = do
personList <- runDB $ selectList [] []
return $ map (\(Entity _ u) -> u) personList
selectPersonById :: Int -> Handler Person
selectPersonById id = do
sqlResult <- runDB $ get $ PersonKey id
case sqlResult of
Just person -> return person
Nothing -> throwError err404 { errBody = JSON.encode "Person with ID not found." }
createPerson :: Person -> Handler Person
createPerson person = do
attemptCreate <- runDB $ insert person
case attemptCreate of
PersonKey k -> return person
_ -> throwError err503 { errBody = JSON.encode "Could not create Person." }
deletePerson :: Int -> Handler ()
deletePerson id = do runDB $ delete $ PersonKey id
startApp :: IO ()
startApp = do
args <- getArgs
let arg1 = if not (null args) then Just (head args) else Nothing
case arg1 of
Just "migrate" -> doMigration
_ -> run 8080 app
```
## Sample requests
Assuming that you have the db running and have first run `stack exec run migrate`, the following sample requests will test your API:
*Create a person*
```bash
curl -X POST \
http://localhost:8080/person \
-H 'Accept: */*' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Content-Length: 62' \
-H 'Content-Type: application/json' \
-H 'Host: localhost:8080' \
-H 'cache-control: no-cache' \
-d '{
"name": "Jake",
"age": "25"
}'
```
*Get all persons*
```bash
curl -X GET \
http://localhost:8080/person \
-H 'Accept: */*' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Content-Length: 33' \
-H 'Content-Type: application/json' \
-H 'Host: localhost:8080' \
-H 'cache-control: no-cache'
```
*Get person by ID*
```bash
curl -X GET \
http://localhost:8080/person/1 \
-H 'Accept: */*' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json' \
-H 'Host: localhost:8080' \
-H 'cache-control: no-cache'
```

View file

@ -0,0 +1,40 @@
cabal-version: 2.2
name: mysql-basics
version: 0.1.0.0
synopsis: Simple MySQL API cookbook example
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
executable run
hs-source-dirs: .
main-is: MysqlBasics.hs
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends: aeson
, base
, bytestring
, http-client
, monad-logger
, mysql-simple
, persistent
, persistent-mysql
, persistent-template
, resource-pool
, resourcet
, servant
, servant-client
, servant-server
, text
, transformers
, wai
, warp
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit
source-repository head
type: git
location: https://github.com/githubuser/mysql-basics

View file

@ -1,14 +1,14 @@
cabal-version: 2.2
name: cookbook-db-postgres-pool name: cookbook-db-postgres-pool
version: 0.1 version: 0.1
synopsis: Simple PostgreSQL connection pool cookbook example synopsis: Simple PostgreSQL connection pool cookbook example
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-db-postgres-pool executable cookbook-db-postgres-pool
main-is: PostgresPool.lhs main-is: PostgresPool.lhs

View file

@ -1,14 +1,14 @@
cabal-version: 2.2
name: cookbook-db-sqlite-simple name: cookbook-db-sqlite-simple
version: 0.1 version: 0.1
synopsis: Simple SQLite DB cookbook example synopsis: Simple SQLite DB cookbook example
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-db-sqlite-simple executable cookbook-db-sqlite-simple
main-is: DBConnection.lhs main-is: DBConnection.lhs
@ -23,7 +23,7 @@ executable cookbook-db-sqlite-simple
, http-types >= 0.12 , http-types >= 0.12
, markdown-unlit >= 0.4 , markdown-unlit >= 0.4
, http-client >= 0.5 , http-client >= 0.5
, sqlite-simple >= 0.4 , sqlite-simple >= 0.4.5.0
, transformers , transformers
default-language: Haskell2010 default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit ghc-options: -Wall -pgmL markdown-unlit

View file

@ -17,7 +17,7 @@ import Control.Exception
import Control.Monad import Control.Monad
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8)
import Network (withSocketsDo) import Network.Socket (withSocketsDo)
import Network.HTTP.Client hiding (Proxy) import Network.HTTP.Client hiding (Proxy)
import Network.HTTP.Client.MultipartFormData import Network.HTTP.Client.MultipartFormData
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
@ -90,8 +90,8 @@ startServer = run 8080 (serve api upload)
Finally, a main function that brings up our server and Finally, a main function that brings up our server and
sends some test request with `http-client` (and not sends some test request with `http-client` (and not
servant-client this time, has servant-multipart does not servant-client this time, as servant-multipart does not
yet have support for client generation. yet have support for client generation).
``` haskell ``` haskell
main :: IO () main :: IO ()
@ -126,7 +126,7 @@ Content of "README.md"
## Getting Started ## Getting Started
We have a [tutorial](http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html) that We have a [tutorial](http://docs.servant.dev/en/stable/tutorial/index.html) that
introduces the core features of servant. After this article, you should be able introduces the core features of servant. After this article, you should be able
to write your first servant webservices, learning the rest from the haddocks' to write your first servant webservices, learning the rest from the haddocks'
examples. examples.

View file

@ -1,14 +1,14 @@
cabal-version: 2.2
name: cookbook-file-upload name: cookbook-file-upload
version: 0.1 version: 0.1
synopsis: File upload cookbook example synopsis: File upload cookbook example
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-file-upload executable cookbook-file-upload
main-is: FileUpload.lhs main-is: FileUpload.lhs

View file

@ -0,0 +1,141 @@
# Using generics
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
module Main (main, api, getLink, routesLinks, cliGet) where
import Control.Exception (throwIO)
import Control.Monad.Trans.Reader (ReaderT, runReaderT)
import Data.Proxy (Proxy (..))
import Network.Wai.Handler.Warp (run)
import System.Environment (getArgs)
import Servant
import Servant.Client
import Servant.API.Generic
import Servant.Client.Generic
import Servant.Server.Generic
```
The usage is simple, if you only need a collection of routes.
First you define a record with field types prefixed by a parameter `route`:
```haskell
data Routes route = Routes
{ _get :: route :- Capture "id" Int :> Get '[JSON] String
, _put :: route :- ReqBody '[JSON] Int :> Put '[JSON] Bool
}
deriving (Generic)
```
Then we'll use this data type to define API, links, server and client.
## API
You can get a `Proxy` of the API using `genericApi`:
```haskell
api :: Proxy (ToServantApi Routes)
api = genericApi (Proxy :: Proxy Routes)
```
It's recommended to use `genericApi` function, as then you'll get
better error message, for example if you forget to `derive Generic`.
## Links
The clear advantage of record-based generics approach, is that
we can get safe links very conveniently. We don't need to define endpoint types,
as field accessors work as proxies:
```haskell
getLink :: Int -> Link
getLink = fieldLink _get
```
We can also get all links at once, as a record:
```haskell
routesLinks :: Routes (AsLink Link)
routesLinks = allFieldLinks
```
## Client
Even more power starts to show when we generate a record of client functions.
Here we use `genericClientHoist` function, which lets us simultaneously
hoist the monad, in this case from `ClientM` to `IO`.
```haskell
cliRoutes :: Routes (AsClientT IO)
cliRoutes = genericClientHoist
(\x -> runClientM x env >>= either throwIO return)
where
env = error "undefined environment"
cliGet :: Int -> IO String
cliGet = _get cliRoutes
```
## Server
Finally, probably the most handy usage: we can convert record of handlers into
the server implementation:
```haskell
record :: Routes AsServer
record = Routes
{ _get = return . show
, _put = return . odd
}
app :: Application
app = genericServe record
main :: IO ()
main = do
args <- getArgs
case args of
("run":_) -> do
putStrLn "Starting cookbook-generic at http://localhost:8000"
run 8000 app
-- see this cookbook below for custom-monad explanation
("run-custom-monad":_) -> do
putStrLn "Starting cookbook-generic with a custom monad at http://localhost:8000"
run 8000 (appMyMonad AppCustomState)
_ -> putStrLn "To run, pass 'run' argument: cabal new-run cookbook-generic run"
```
## Using generics together with a custom monad
If your app uses a custom monad, here's how you can combine it with
generics.
```haskell
data AppCustomState =
AppCustomState
type AppM = ReaderT AppCustomState Handler
apiMyMonad :: Proxy (ToServantApi Routes)
apiMyMonad = genericApi (Proxy :: Proxy Routes)
getRouteMyMonad :: Int -> AppM String
getRouteMyMonad = return . show
putRouteMyMonad :: Int -> AppM Bool
putRouteMyMonad = return . odd
recordMyMonad :: Routes (AsServerT AppM)
recordMyMonad = Routes {_get = getRouteMyMonad, _put = putRouteMyMonad}
-- natural transformation
nt :: AppCustomState -> AppM a -> Handler a
nt s x = runReaderT x s
appMyMonad :: AppCustomState -> Application
appMyMonad state = genericServeT (nt state) recordMyMonad

View file

@ -0,0 +1,25 @@
cabal-version: 2.2
name: cookbook-generic
version: 0.1
synopsis: Using custom monad to pass a state between handlers
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-using-custom-monad
main-is: Generic.lhs
build-depends: base == 4.*
, servant
, servant-client
, servant-client-core
, servant-server
, base-compat
, warp >= 3.2
, transformers >= 0.3
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4

View file

@ -0,0 +1,413 @@
# Hoist Server With Context for Custom Monads
In this example we'll combine some of the patterns we've seen in other examples
in order to demonstrate using a custom monad with Servant's `Context` and the function
`hoistServerWithContext`.
`hoistServerWithContext` is a pattern you may encounter if you are trying to use a library such as
[servant-auth-server](https://hackage.haskell.org/package/servant-auth-server) along
with your own custom monad.
In this example, our custom monad will be based on the commonly used `ReaderT env IO a` stack.
We'll create an `AppCtx` to represent our `env` and include some logging utilities as well as
other variables we'd like to have available.
In addition, in order to demonstrate a custom `Context`, we'll also include authentication in
our example. As noted previously (in [jwt-and-basic-auth](../jwt-and-basic-auth/JWTAndBasicAuth.lhs)),
while basic authentication comes with Servant itself,
[servant-auth](https://hackage.haskell.org/package/servant-auth) and
[servant-auth-server](https://hackage.haskell.org/package/servant-auth-server)
packages are needed for JWT-based authentication.
Finally, we're going to use [fast-logger](http://hackage.haskell.org/package/fast-logger)
for our logging example below.
This recipe uses the following ingredients:
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
import Prelude ()
import Prelude.Compat
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader
import Data.Aeson
import Data.Default
import Data.Proxy
import Data.Text
import Data.Time.Clock ( UTCTime, getCurrentTime )
import GHC.Generics
import Network.Wai (Middleware)
import Network.Wai.Handler.Warp as Warp
import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.RequestLogger.JSON
import Servant as S
import Servant.Auth as SA
import Servant.Auth.Server as SAS
import System.Log.FastLogger ( ToLogStr(..)
, LoggerSet
, defaultBufSize
, newStdoutLoggerSet
, flushLogStr
, pushLogStrLn )
port :: Int
port = 3001
```
## Custom Monad
Let's say we'd like to create a custom monad based on `ReaderT env` in order to hold
access to a config object as well as some logging utilities.
With that, we could define an `AppCtx` and `AppM` like this:
```haskell
type AppM = ReaderT AppCtx Handler
data AppCtx = AppCtx {
_getConfig :: SiteConfig
, _getLogger :: LoggerSet
}
data SiteConfig = SiteConfig {
environment :: !Text
, version :: !Text
, adminUsername :: !Text
, adminPasswd :: !Text
} deriving (Generic, Show)
```
This `SiteConfig` is a simple example: it refers to our deployment environment as well as an
application version. For instance, we may do something different based on the environment our app is
deployed into. When emitting log messages, we may want to include information about
the deployed version of our application.
In addition, we're going to identify a single admin user in our config and use
that definition to authenticate requests inside our handlers. This is not too
flexible (and probably not too secure...), but it works as a simple example.
## Logging
A common contemporary pattern is to emit log messages as JSON for later ingestion
into a database like Elasticsearch.
To emit JSON log messages, we'll create a `LogMessage` object and make it so we can turn it
into a JSON-encoded `LogStr` (a type from `fast-logger`).
```haskell
data LogMessage = LogMessage {
message :: !Text
, timestamp :: !UTCTime
, level :: !Text
, lversion :: !Text
, lenvironment :: !Text
} deriving (Eq, Show, Generic)
instance FromJSON LogMessage
instance ToJSON LogMessage where
toEncoding = genericToEncoding defaultOptions
instance ToLogStr LogMessage where
toLogStr = toLogStr . encode
```
Eventually, when we'd like to emit a log message inside one of our Handlers, it'll look like this:
```haskell
sampleHandler :: AppM LogMessage
sampleHandler = do
config <- asks _getConfig
logset <- asks _getLogger
tstamp <- liftIO getCurrentTime
let logMsg = LogMessage { message = "let's do some logging!"
, timestamp = tstamp
, level = "info"
, lversion = version config
, lenvironment = environment config
}
-- emit log message
liftIO $ pushLogStrLn logset $ toLogStr logMsg
-- return handler result (for simplicity, result is also a LogMessage)
pure logMsg
```
## Authentication
To demonstrate the other part of this recipe, we are going to use a simple
representation of a user, someone who may have access to an admin section of our site:
```haskell
data AdminUser = AdminUser { name :: Text }
deriving (Eq, Show, Read, Generic)
```
The following instances are needed for JWT:
```haskell
instance ToJSON AdminUser
instance FromJSON AdminUser
instance SAS.ToJWT AdminUser
instance SAS.FromJWT AdminUser
```
## API
Now we can define our API.
We'll have an `admin` endpoint and a `login` endpoint that takes a `LoginForm`:
```haskell
type AdminApi =
"admin" :> Get '[JSON] LogMessage
type LoginApi =
"login"
:> ReqBody '[JSON] LoginForm
:> Post '[JSON] (Headers '[ Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] LogMessage)
data LoginForm = LoginForm {
username :: Text
, password :: Text
} deriving (Eq, Show, Generic)
instance ToJSON LoginForm
instance FromJSON LoginForm
```
We can combine both APIs into one like so:
```haskell
type AdminAndLogin auths = (SAS.Auth auths AdminUser :> AdminApi) :<|> LoginApi
```
## Server
When we define our server, we'll have to define handlers for the `AdminApi` and the `LoginApi` and
we'll have to supply `JWTSettings` and `CookieSettings` so our `login` handler can authenticate users:
```haskell
adminServer :: SAS.CookieSettings -> SAS.JWTSettings -> ServerT (AdminAndLogin auths) AppM
adminServer cs jwts = adminHandler :<|> loginHandler cs jwts
```
The `admin` route should receive an authenticated `AdminUser` as an argument
or it should return a `401`:
```haskell
adminHandler :: AuthResult AdminUser -> AppM LogMessage
adminHandler (SAS.Authenticated adminUser) = do
config <- asks _getConfig
logset <- asks _getLogger
tstamp <- liftIO getCurrentTime
let logMsg = LogMessage { message = "Admin User accessing admin: " <> name adminUser
, timestamp = tstamp
, level = "info"
, lversion = version config
, lenvironment = environment config
}
-- emit log message
liftIO $ pushLogStrLn logset $ toLogStr logMsg
-- return handler result (for simplicity, result is a LogMessage)
pure logMsg
adminHandler _ = throwError err401
```
By contrast, the `login` handler is waiting for a `POST` with a login form.
If login is successful, it will set session cookies and return a value.
Here we're going to include lots of log messages:
```haskell
loginHandler :: CookieSettings
-> JWTSettings
-> LoginForm
-> AppM (Headers '[ Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] LogMessage)
loginHandler cookieSettings jwtSettings form = do
config <- asks _getConfig
logset <- asks _getLogger
tstamp <- liftIO getCurrentTime
let logMsg = LogMessage { message = "AdminUser login attempt failed!"
, timestamp = tstamp
, level = "info"
, lversion = version config
, lenvironment = environment config
}
case validateLogin config form of
Nothing -> do
liftIO $ pushLogStrLn logset $ toLogStr logMsg
throwError err401
Just usr -> do
mApplyCookies <- liftIO $ SAS.acceptLogin cookieSettings jwtSettings usr
case mApplyCookies of
Nothing -> do
liftIO $ pushLogStrLn logset $ toLogStr logMsg
throwError err401
Just applyCookies -> do
let successMsg = logMsg{message = "AdminUser successfully authenticated!"}
liftIO $ pushLogStrLn logset $ toLogStr successMsg
pure $ applyCookies successMsg
loginHandler _ _ _ = throwError err401
validateLogin :: SiteConfig -> LoginForm -> Maybe AdminUser
validateLogin config (LoginForm uname passwd ) =
if (uname == adminUsername config) && (passwd == adminPasswd config)
then Just $ AdminUser uname
else Nothing
```
## `serveWithContext` and `hoistServerWithContext`
In order to build a working server, we'll need to `hoist` our custom monad
into Servant's Handler monad. We'll also need to pass in the proper context to ensure
authentication will work.
This will require both `serveWithContext` and `hoistServerWithContext`.
Let's define the function which will create our `Application`:
```haskell
adminLoginApi :: Proxy (AdminAndLogin '[JWT])
adminLoginApi = Proxy
mkApp :: Context '[SAS.CookieSettings, SAS.JWTSettings] -> CookieSettings -> JWTSettings -> AppCtx -> Application
mkApp cfg cs jwts ctx =
serveWithContext adminLoginApi cfg $
hoistServerWithContext adminLoginApi (Proxy :: Proxy '[SAS.CookieSettings, SAS.JWTSettings])
(flip runReaderT ctx) (adminServer cs jwts)
```
One footnote: because we'd like our logs to be in JSON form, we'll also create a `Middleware` object
so that `Warp` *also* will emit logs as JSON. This will ensure *all* logs are emitted as JSON:
```haskell
jsonRequestLogger :: IO Middleware
jsonRequestLogger =
mkRequestLogger $ def { outputFormat = CustomOutputFormatWithDetails formatAsJSON }
```
We now have all the pieces we need to serve our application inside a `main` function:
```haskell
main :: IO ()
main = do
-- typically, we'd create our config from environment variables
-- but we're going to just make one here
let config = SiteConfig "dev" "1.0.0" "admin" "secretPassword"
warpLogger <- jsonRequestLogger
appLogger <- newStdoutLoggerSet defaultBufSize
tstamp <- getCurrentTime
myKey <- generateKey
let lgmsg = LogMessage {
message = "My app starting up!"
, timestamp = tstamp
, level = "info"
, lversion = version config
, lenvironment = environment config
}
pushLogStrLn appLogger (toLogStr lgmsg) >> flushLogStr appLogger
let ctx = AppCtx config appLogger
warpSettings = Warp.defaultSettings
portSettings = Warp.setPort port warpSettings
settings = Warp.setTimeout 55 portSettings
jwtCfg = defaultJWTSettings myKey
cookieCfg = if environment config == "dev"
then defaultCookieSettings{cookieIsSecure=SAS.NotSecure}
else defaultCookieSettings
cfg = cookieCfg :. jwtCfg :. EmptyContext
Warp.runSettings settings $ warpLogger $ mkApp cfg cookieCfg jwtCfg ctx
```
## Usage
Now we can run it and try it out with `curl`. In one terminal, let's run our application
and see what our log output looks like:
```$ ./cookbook-hoist-server-with-context
{"message":"My app starting up!","timestamp":"2018-10-04T00:33:12.482568Z","level":"info","lversion":"1.0.0","lenvironment":"dev"}
```
In another terminal, let's ensure that it fails with `err401` if
we're not authenticated:
```
$ curl -v 'http://localhost:3001/admin'
< HTTP/1.1 401 Unauthorized
```
```
$ curl -v -XPOST 'http://localhost:3001/login' \
-H "Content-Type:application/json" \
-d '{"username": "bad", "password": "wrong"}'
< HTTP/1.1 401 Unauthorized
```
And in the other terminal with our log messages (from our JSON `Middleware`):
```
{"time":"03/Oct/2018:17:35:56 -0700","response":{"status":401,"size":null,"body":""},"request":{"httpVersion":"1.1","path":"/admin","size":0,"body":"","durationMs":0.22,"remoteHost":{"hostAddress":"127.0.0.1","port":51029},"headers":[["Host","localhost:3001"],["User-Agent","curl/7.60.0"],["Accept","*/*"]],"queryString":[],"method":"GET"}}
```
Now let's see that authentication works, and that we get JWTs:
```
$ curl -v -XPOST 'http://localhost:3001/login' \
-H "Content-Type:application/json" \
-d '{"username": "admin", "password": "secretPassword"}'
< HTTP/1.1 200 OK
...
< Server: Warp/3.2.25
< Content-Type: application/json;charset=utf-8
< Set-Cookie: JWT-Cookie=eyJhbGciOiJIUzUxMiJ9.eyJkYXQiOnsibmFtZSI6ImFkbWluIn19.SIoRcABKSO4mXnRifzqPWlHJUhVwuy32Qon7s1E_c3vHOsLXdXyX4V4eXOw9tMFoeIqgsXMZucqoFb36vAdKwQ; Path=/; HttpOnly; SameSite=Lax
< Set-Cookie: XSRF-TOKEN=y5PmrYHX3ywFUCwGRQqHh1TDheTLiQpwRQB3FFRd8N4=; Path=/
...
{"message":"AdminUser succesfully authenticated!","timestamp":"2018-10-04T00:37:44.455441Z","level":"info","lversion":"1.0.0","lenvironment":"dev"}
```
And in the other terminal with our log messages (note that logging out passwords is insecure...):
```
{"message":"AdminUser succesfully authenticated!","timestamp":"2018-10-04T00:37:44.455441Z","level":"info","lversion":"1.0.0","lenvironment":"dev"}
{"time":"03/Oct/2018:17:37:44 -0700","response":{"status":200,"size":null,"body":null},"request":{"httpVersion":"1.1","path":"/login","size":51,"body":"{\"username\": \"admin\", \"password\": \"secretPassword\"}","durationMs":0.23,"remoteHost":{"hostAddress":"127.0.0.1","port":51044},"headers":[["Host","localhost:3001"],["User-Agent","curl/7.60.0"],["Accept","*/*"],["Content-Type","application/json"],["Content-Length","51"]],"queryString":[],"method":"POST"}}
```
Finally, let's make sure we can access a protected resource with our tokens:
```
$ export jwt=eyJhbGciOiJIUzUxMiJ9.eyJkYXQiOnsibmFtZSI6ImFkbWluIn19.SIoRcABKSO4mXnRifzqPWlHJUhVwuy32Qon7s1E_c3vHOsLXdXyX4V4eXOw9tMFoeIqgsXMZucqoFb36vAdKwQ
$ curl -v \
-H "Authorization: Bearer $jwt" \
'http://localhost:3001/admin'
< HTTP/1.1 200 OK
{"message":"Admin User accessing admin: admin","timestamp":"2018-10-04T00:58:07.216605Z","level":"info","lversion":"1.0.0","lenvironment":"dev"}
```
And we should see this message logged-out as well:
```
{"message":"Admin User accessing admin: admin","timestamp":"2018-10-04T00:58:07.216605Z","level":"info","lversion":"1.0.0","lenvironment":"dev"}
```
This program is available as a cabal project
[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/hoist-server-with-context).

View file

@ -0,0 +1,37 @@
cabal-version: 2.2
name: cookbook-hoist-server-with-context
version: 0.0.1
synopsis: JWT and basic access authentication with a Custom Monad cookbook example
description: Using servant-auth to support both JWT-based and basic
authentication.
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
category: Servant
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-hoist-server-with-context
main-is: HoistServerWithContext.lhs
build-depends: base == 4.*
, base-compat
, text >= 1.2
, aeson >= 1.2
, data-default
, fast-logger
, servant
, servant-server
, servant-auth >= 0.3.2
, servant-auth-server >= 0.4.4.0
, time
, warp >= 3.2
, wai >= 3.2
, wai-extra
, http-types >= 0.12
, bytestring >= 0.10.4
, mtl
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit

View file

@ -34,16 +34,16 @@ app = serve api server
``` ```
It's now time to actually run the `Application`. It's now time to actually run the `Application`.
The [`warp-tls`](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html) The [`warp-tls`](https://hackage.haskell.org/package/warp-tls/docs/Network-Wai-Handler-WarpTLS.html)
package provides two functions for running an `Application`, called package provides two functions for running an `Application`, called
[`runTLS`](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html#v:runTLS) [`runTLS`](https://hackage.haskell.org/package/warp-tls/docs/Network-Wai-Handler-WarpTLS.html#v:runTLS)
and [`runTLSSocket`](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html#v:runTLSSocket). and [`runTLSSocket`](https://hackage.haskell.org/package/warp-tls/docs/Network-Wai-Handler-WarpTLS.html#v:runTLSSocket).
We will be using the first one. We will be using the first one.
It takes two arguments, It takes two arguments,
[the TLS settings](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html#t:TLSSettings) [the TLS settings](https://hackage.haskell.org/package/warp-tls/docs/Network-Wai-Handler-WarpTLS.html#t:TLSSettings)
(certificates, keys, ciphers, etc) (certificates, keys, ciphers, etc)
and [the warp settings](https://hackage.haskell.org/package/warp-3.2.12/docs/Network-Wai-Handler-Warp-Internal.html#t:Settings) and [the warp settings](https://hackage.haskell.org/package/warp/docs/Network-Wai-Handler-Warp-Internal.html#t:Settings)
(port, logger, etc). (port, logger, etc).
We will be using very simple settings for this example but you are of We will be using very simple settings for this example but you are of

View file

@ -1,14 +1,14 @@
cabal-version: 2.2
name: cookbook-https name: cookbook-https
version: 0.1 version: 0.1
synopsis: HTTPS cookbook example synopsis: HTTPS cookbook example
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-https executable cookbook-https
main-is: Https.lhs main-is: Https.lhs
@ -17,7 +17,7 @@ executable cookbook-https
, servant-server , servant-server
, wai >= 3.2 , wai >= 3.2
, warp >= 3.2 , warp >= 3.2
, warp-tls >= 3.2 , warp-tls >= 3.2.9
, markdown-unlit >= 0.4 , markdown-unlit >= 0.4
default-language: Haskell2010 default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit ghc-options: -Wall -pgmL markdown-unlit

View file

@ -6,8 +6,8 @@ how to solve many common problems with servant. If you're
interested in contributing examples of your own, feel free interested in contributing examples of your own, feel free
to open an issue or a pull request on to open an issue or a pull request on
`our github repository <https://github.com/haskell-servant/servant>`_ `our github repository <https://github.com/haskell-servant/servant>`_
or even to just get in touch with us on the **#servant** IRC channel or even to just get in touch with us on the `**#haskell-servant** IRC channel
on freenode or on on libera.chat <https://web.libera.chat/#haskell-servant>_ or on
`the mailing list <https://groups.google.com/forum/#!forum/haskell-servant>`_. `the mailing list <https://groups.google.com/forum/#!forum/haskell-servant>`_.
The scope is very wide. Simple and fancy authentication schemes, The scope is very wide. Simple and fancy authentication schemes,
@ -18,10 +18,23 @@ you name it!
:maxdepth: 1 :maxdepth: 1
structuring-apis/StructuringApis.lhs structuring-apis/StructuringApis.lhs
generic/Generic.lhs
https/Https.lhs https/Https.lhs
db-mysql-basics/MysqlBasics.lhs
db-sqlite-simple/DBConnection.lhs db-sqlite-simple/DBConnection.lhs
db-postgres-pool/PostgresPool.lhs db-postgres-pool/PostgresPool.lhs
using-custom-monad/UsingCustomMonad.lhs using-custom-monad/UsingCustomMonad.lhs
using-free-client/UsingFreeClient.lhs
custom-errors/CustomErrors.lhs
uverb/UVerb.lhs
basic-auth/BasicAuth.lhs basic-auth/BasicAuth.lhs
basic-streaming/Streaming.lhs
jwt-and-basic-auth/JWTAndBasicAuth.lhs jwt-and-basic-auth/JWTAndBasicAuth.lhs
hoist-server-with-context/HoistServerWithContext.lhs
file-upload/FileUpload.lhs file-upload/FileUpload.lhs
pagination/Pagination.lhs
curl-mock/CurlMock.lhs
sentry/Sentry.lhs
testing/Testing.lhs
open-id-connect/OpenIdConnect.lhs
managed-resource/ManagedResource.lhs

View file

@ -1,22 +1,19 @@
cabal-version: 2.2
name: cookbook-jwt-and-basic-auth name: cookbook-jwt-and-basic-auth
version: 0.0.1 version: 0.0.1
synopsis: JWT and basic access authentication cookbook example synopsis: JWT and basic access authentication cookbook example
description: Using servant-auth to support both JWT-based and basic description: Using servant-auth to support both JWT-based and basic
authentication. authentication.
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
category: Servant category: Servant
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-jwt-and-basic-auth executable cookbook-jwt-and-basic-auth
if !impl(ghc >= 7.10)
buildable: False
main-is: JWTAndBasicAuth.lhs main-is: JWTAndBasicAuth.lhs
build-depends: base == 4.* build-depends: base == 4.*
, text >= 1.2 , text >= 1.2
@ -25,7 +22,7 @@ executable cookbook-jwt-and-basic-auth
, servant , servant
, servant-client , servant-client
, servant-server , servant-server
, servant-auth ==0.3.* , servant-auth == 0.4.*
, servant-auth-server >= 0.3.1.0 , servant-auth-server >= 0.3.1.0
, warp >= 3.2 , warp >= 3.2
, wai >= 3.2 , wai >= 3.2

View file

@ -0,0 +1,114 @@
# Request-lifetime Managed Resources
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.
As usual, we start with a little bit of throat clearing.
``` haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
import Control.Concurrent
import Control.Exception (bracket, throwIO)
import Control.Monad.IO.Class
import Control.Monad.Trans.Resource
import Data.Acquire
import Network.HTTP.Client (newManager, defaultManagerSettings)
import Network.Wai.Handler.Warp
import Servant
import Servant.Client
import System.IO
```
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.
``` haskell
type API = WithResource Handle :> ReqBody '[PlainText] String :> Post '[JSON] NoContent
api :: Proxy API
api = Proxy
```
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.
``` haskell
appContext :: Context '[Acquire Handle]
appContext = acquireHandle :. EmptyContext
acquireHandle :: Acquire Handle
acquireHandle = mkAcquire newHandle closeHandle
newHandle :: IO Handle
newHandle = do
putStrLn "opening file"
h <- openFile "test.txt" AppendMode
putStrLn "opened file"
return h
closeHandle :: Handle -> IO ()
closeHandle h = do
putStrLn "closing file"
hClose h
putStrLn "closed file"
```
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.
``` haskell
server :: Server API
server = writeToFile
where writeToFile :: (ReleaseKey, Handle) -> String -> Handler NoContent
writeToFile (_, h) msg = case msg of
"illegal" -> error "wait, that's illegal!"
legalMsg -> liftIO $ do
putStrLn "writing file"
hPutStrLn h legalMsg
putStrLn "wrote file"
return NoContent
```
Finally we run the server in the background while we post messages to it.
``` haskell
runApp :: IO ()
runApp = run 8080 (serveWithContext api appContext $ server)
postMsg :: String -> ClientM NoContent
postMsg = client api
main :: IO ()
main = do
mgr <- newManager defaultManagerSettings
bracket (forkIO $ runApp) killThread $ \_ -> do
ms <- flip runClientM (mkClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do
liftIO $ putStrLn "sending hello message"
_ <- postMsg "hello"
liftIO $ putStrLn "sending illegal message"
_ <- postMsg "illegal"
liftIO $ putStrLn "done"
print ms
```
This program prints
```
sending hello message
opening file
opened file
writing file
wrote file
closing file
closed file
sending illegal message
opening file
opened file
closing file
closed file
wait, that's illegal!
CallStack (from HasCallStack):
error, called at ManagedResource.lhs:63:24 in main:Main
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"}))
```
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.

View file

@ -0,0 +1,30 @@
cabal-version: 2.2
name: cookbook-managed-resource
version: 0.1
synopsis: Simple managed resource cookbook example
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==9.4.2
executable cookbook-managed-resource
main-is: ManagedResource.lhs
build-depends: base == 4.*
, text >= 1.2
, aeson >= 1.2
, servant
, servant-client
, servant-server
, warp >= 3.2
, wai >= 3.2
, http-types >= 0.12
, markdown-unlit >= 0.4
, http-client >= 0.5
, transformers
, resourcet
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit

View file

@ -0,0 +1,45 @@
cabal-version: 2.2
name: open-id-connect
version: 0.1
synopsis: OpenId Connect with Servant example
homepage: http://haskell-servant.readthedocs.org/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5
executable cookbook-openidconnect
main-is: OpenIdConnect.lhs
build-depends: base ==4.*
, aeson
, aeson-pretty
, binary
, blaze-html
, blaze-markup
, bytestring
, case-insensitive
, cereal
, containers
, generic-lens
, http-client
, http-client-tls
, http-types
, jose-jwt
, lens
, lens-aeson
, oidc-client
, protolude
, random
, servant
, servant-blaze
, servant-server
, text
, time
, vector
, wai
, warp >= 3.2
default-language: Haskell2010
ghc-options: -Wall -Wcompat -Wincomplete-uni-patterns -Wredundant-constraints -Wnoncanonical-monad-instances -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4

View file

@ -0,0 +1,472 @@
[OpenID Connect](https://openid.net/connect/)
=============================================
Use OpenID Connect to authenticate your users.
This example use google OIDC provider.
It was made for a working with single page application where
some login token would be saved in the user agent local storage.
Workflow:
1. user is presented with a login button,
2. when the user clicks on the button it is redirected to the OIDC
provider,
3. the user login in the OIDC provider,
4. the OIDC provider will redirect the user and provide a `code`,
5. the server will use this code to make a POST to the OIDC provider
and will get back authentication infos,
6. The user will get display an HTML page that will save a secret
identifying him in the local storage, then it will be redirected to
/.
Let's put the imports behind us:
``` haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Main where
import Protolude
import Data.Aeson
(FromJSON (..), (.:))
import qualified Data.Aeson as JSON
import qualified Data.Aeson.Types as AeT
import qualified Data.ByteString.Lazy as LBS
import qualified Data.List as List
import qualified Data.Text as Text
import Jose.Jwt
(Jwt (..), decodeClaims)
import Network.HTTP.Client
(Manager, newManager)
import Network.HTTP.Client.TLS
(tlsManagerSettings)
import Network.Wai.Handler.Warp
(run)
import Servant
import Servant.HTML.Blaze
(HTML)
import qualified System.Random as Random
import Text.Blaze
(ToMarkup (..))
import qualified Text.Blaze.Html as H
import Text.Blaze.Html5
((!))
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as HA
import Text.Blaze.Renderer.Utf8
(renderMarkup)
import qualified Web.OIDC.Client as O
```
You'll need to create a new OpenID Connect client in an OpenID Provider.
This example was tested with Google.
You can find a list of public OIDC provider here:
https://connect2id.com/products/nimbus-oauth-openid-connect-sdk/openid-connect-providers
I copied some here:
- Google: https://developers.google.com/identity/protocols/OpenIDConnect
more precisely: https://console.developers.google.com/apis/credentials
- Microsoft: https://docs.microsoft.com/en-us/previous-versions/azure/dn645541(v=azure.100)
- Yahoo: https://developer.yahoo.com/oauth2/guide/openid_connect/
- PayPal: https://developer.paypal.com/docs/integration/direct/identity/log-in-with-paypal/
During the configuration you'll need to provide a redirect uri.
The redirect_uri should correspond to the uri user will be redirected to
after a successful login into the OpenID provider.
So during your test, you should certainly just use `http://localhost:3000/login/cb`.
In general you should use your own domain name.
You'll then be given a `client_id` and a `client_password`.
Fill those values in here:
``` haskell
oidcConf :: OIDCConf
oidcConf = OIDCConf { redirectUri = "http://localhost:3000/login/cb"
, clientId = "xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
, clientPassword = "************************" }
```
Then we declare our main server:
``` haskell
main :: IO ()
main = do
oidcEnv <- initOIDC oidcConf
run 3000 (app oidcEnv)
type API = IdentityRoutes Customer
:<|> Get '[HTML] Homepage
api :: Proxy API
api = Proxy
server :: OIDCEnv -> Server API
server oidcEnv = serveOIDC oidcEnv handleOIDCLogin
:<|> return Homepage
-- | Then main app
app :: OIDCEnv -> Application
app oidcEnv = serve api (server oidcEnv)
```
OIDC
----
That part try to separate concern, and certainly in a real world
application that should be in its distinct module.
``` haskell
-- * OIDC
data OIDCConf =
OIDCConf { redirectUri :: ByteString
, clientId :: ByteString
, clientPassword :: ByteString
} deriving (Show, Eq)
```
First we need to initialize OIDC.
A short explanation about it:
- to complete the workflow we need to make a POST request to the OIDC provider.
So we need to create an http manager to make those call properly.
- Then in order to prevent replay attack, each time an user wants to login we
should provide a random string called the `state`. When the user is
redirected to the `redirect_uri`, the OIDC provider should provide the same
`state` along a `code` parameter.
``` haskell
initOIDC :: OIDCConf -> IO OIDCEnv
initOIDC OIDCConf{..} = do
mgr <- newManager tlsManagerSettings
prov <- O.discover "https://accounts.google.com" mgr
let oidc = O.setCredentials clientId clientPassword redirectUri (O.newOIDC prov)
return OIDCEnv { oidc = oidc
, mgr = mgr
, genState = genRandomBS
, prov = prov
, redirectUri = redirectUri
, clientId = clientId
, clientPassword = clientPassword
}
data OIDCEnv = OIDCEnv { oidc :: O.OIDC
, mgr :: Manager
, genState :: IO ByteString
, prov :: O.Provider
, redirectUri :: ByteString
, clientId :: ByteString
, clientPassword :: ByteString
}
```
The `IdentityRoutes` are two endpoints:
- an endpoint to redirect the users to the OIDC Provider,
- another one the user will be redirected to from the OIDC Provider.
``` haskell
type IdentityRoutes a =
"login" :> ( -- redirect User to the OpenID Provider
Get '[JSON] NoContent
-- render the page that will save the user creds in the user-agent
:<|> "cb" :> QueryParam "error" Text
:> QueryParam "code" Text
:> Get '[HTML] User)
-- | gen a 302 redirect helper
redirects :: (StringConv s ByteString) => s -> Handler ()
redirects url = throwError err302 { errHeaders = [("Location",toS url)]}
```
That function will generate the URL to redirect the users to when
they'll click on the login link: `https://yourdomain/login`.
``` haskell
genOIDCURL :: OIDCEnv -> IO ByteString
genOIDCURL OIDCEnv{..} = do
st <- genState -- generate a random string
let oidcCreds = O.setCredentials clientId clientPassword redirectUri (O.newOIDC prov)
loc <- O.getAuthenticationRequestUrl oidcCreds [O.openId, O.email, O.profile] (Just st) []
return (show loc)
handleLogin :: OIDCEnv -> Handler NoContent
handleLogin oidcenv = do
loc <- liftIO (genOIDCURL oidcenv)
redirects loc
return NoContent
```
The `AuthInfo` is about the infos we can grab from OIDC provider.
To be more precise, the user should come with a `code` (a token) and
POSTing that code to the correct OIDC provider endpoint should return a JSON
object. One of the fields should be named `id_token` which should be a
JWT containing all the information we need. Depending on the scopes we
asked we might get more information.
``` haskell
-- | @AuthInfo@
data AuthInfo = AuthInfo { email :: Text
, emailVerified :: Bool
, name :: Text } deriving (Eq, Show, Generic)
instance FromJSON AuthInfo where
parseJSON (JSON.Object v) = do
email :: Text <- v .: "email"
email_verified :: Bool <- v .: "email_verified"
name :: Text <- v .: "name"
return $ AuthInfo (toS email) email_verified (toS name)
parseJSON invalid = AeT.typeMismatch "Coord" invalid
instance JSON.ToJSON AuthInfo where
toJSON (AuthInfo e ev n) =
JSON.object [ "email" JSON..= (toS e :: Text)
, "email_verified" JSON..= ev
, "name" JSON..= (toS n :: Text)
]
type LoginHandler = AuthInfo -> IO (Either Text User)
```
The `handleLoggedIn` is that part that will retrieve the information from
the user once he is redirected from the OIDC Provider after login.
If the user is redirected to the `redirect_uri` but with an `error` query
parameter then it means something went wrong.
If there is no error query param but a `code` query param it means the user
successfully logged in. From there we need to make a request to the token
endpoint of the OIDC provider. It's a POST that should contain the code
as well as the client id and secret.
Making this HTTP POST is the responsibility of `requestTokens`.
From there we extract the `claims` of the JWT contained in one of the value
of the JSON returned by the POST HTTP Request.
``` haskell
data User = User { userId :: Text
, userSecret :: Text
, localStorageKey :: Text
, redirectUrl :: Maybe Text
} deriving (Show,Eq,Ord)
handleLoggedIn :: OIDCEnv
-> LoginHandler -- ^ handle successful id
-> Maybe Text -- ^ error
-> Maybe Text -- ^ code
-> Handler User
handleLoggedIn oidcenv handleSuccessfulId err mcode =
case err of
Just errorMsg -> forbidden errorMsg
Nothing -> case mcode of
Just oauthCode -> do
tokens <- liftIO $ O.requestTokens (oidc oidcenv) (toS oauthCode) (mgr oidcenv)
putText . show . O.claims . O.idToken $ tokens
let jwt = toS . unJwt . O.jwt . O.idToken $ tokens
eAuthInfo = decodeClaims jwt :: Either O.JwtError (O.JwtHeader,AuthInfo)
case eAuthInfo of
Left jwtErr -> forbidden $ "JWT decode/check problem: " <> show jwtErr
Right (_,authInfo) ->
if emailVerified authInfo
then do
user <- liftIO $ handleSuccessfulId authInfo
either forbidden return user
else forbidden "Please verify your email"
Nothing -> do
liftIO $ putText "No code param"
forbidden "no code parameter given"
```
When you render a User with blaze-html, it will generate a page with a js
that will put a secret for that user in the local storage. And it will
redirect the user to /.
``` haskell
instance ToMarkup User where
toMarkup User{..} = H.docTypeHtml $ do
H.head $
H.title "Logged In"
H.body $ do
H.h1 "Logged In"
H.p (H.toHtml ("Successful login with id " <> userId))
H.script (H.toHtml ("localStorage.setItem('" <> localStorageKey <> "','" <> userSecret <> "');"
<> "localStorage.setItem('user-id','" <> userId <> "');"
<> "window.location='" <> fromMaybe "/" redirectUrl <> "';" -- redirect the user to /
));
serveOIDC :: OIDCEnv -> LoginHandler -> Server (IdentityRoutes a)
serveOIDC oidcenv loginHandler =
handleLogin oidcenv :<|> handleLoggedIn oidcenv loginHandler
-- * Auth
type APIKey = ByteString
type Account = Text.Text
type Conf = [(APIKey,Account)]
data Customer = Customer {
account :: Account
, apiKey :: APIKey
, mail :: Maybe Text
, fullname :: Maybe Text
}
```
Here is the code that displays the homepage.
It should contain a link to the `/login` URL.
When the user clicks on this link it will be redirected to Google login page
with some generated information.
The page also displays the content of the local storage.
And in particular the items `api-key` and `user-id`.
Those items should be set after a successful login when the user is redirected to
`/login/cb`.
The logic used generally is to use that api-key to uniquely identify an user.
Another option would have been to set a cookie.
``` haskell
data Homepage = Homepage
instance ToMarkup Homepage where
toMarkup Homepage = H.docTypeHtml $ do
H.head $ do
H.title "OpenID Connect Servant Example"
H.style (H.toHtml ("body { font-family: monospace; font-size: 18px; }" :: Text.Text))
H.body $ do
H.h1 "OpenID Connect Servant Example"
H.div $
H.a ! HA.href "/login" $ "Click here to login"
H.ul $ do
H.li $ do
H.span "API Key in Local storage: "
H.script (H.toHtml ("document.write(localStorage.getItem('api-key'));" :: Text.Text))
H.li $ do
H.span "User ID in Local storage: "
H.script (H.toHtml ("document.write(localStorage.getItem('user-id'));" :: Text.Text))
```
We need some helpers to generate random string for generating state and API Keys.
``` haskell
-- | generate a random ByteString, not necessarily extremely good randomness
-- still the password will be long enough to be very difficult to crack
genRandomBS :: IO ByteString
genRandomBS = do
g <- Random.newStdGen
Random.randomRs (0, n) g & take 42 & fmap toChar & readable 0 & toS & return
where
n = length letters - 1
toChar i = letters List.!! i
letters = ['A'..'Z'] <> ['0'..'9'] <> ['a'..'z']
readable :: Int -> [Char] -> [Char]
readable _ [] = []
readable i str =
let blocksize = case n of
0 -> 8
1 -> 4
2 -> 4
3 -> 4
_ -> 12
block = take blocksize str
rest = drop blocksize str
in if List.null rest
then str
else block <> "-" <> readable (i+1) rest
customerFromAuthInfo :: AuthInfo -> IO Customer
customerFromAuthInfo authinfo = do
apikey <- genRandomBS
return Customer { account = toS (email authinfo)
, apiKey = apikey
, mail = Just (toS (email authinfo))
, fullname = Just (toS (name authinfo))
}
handleOIDCLogin :: LoginHandler
handleOIDCLogin authInfo = do
custInfo <- customerFromAuthInfo authInfo
if emailVerified authInfo
then return . Right . customerToUser $ custInfo
else return (Left "You emails is not verified by your provider. Please verify your email.")
where
customerToUser :: Customer -> User
customerToUser c =
User { userId = toS (account c)
, userSecret = toS (apiKey c)
, redirectUrl = Nothing
, localStorageKey = "api-key"
}
```
`Error` helpers
---------------
``` haskell
data Err = Err { errTitle :: Text
, errMsg :: Text }
instance ToMarkup Err where
toMarkup Err{..} = H.docTypeHtml $ do
H.head $ do
H.title "Error"
H.body $ do
H.h1 (H.a ! HA.href "/" $ "Home")
H.h2 (H.toHtml errTitle)
H.p (H.toHtml errMsg)
format :: ToMarkup a => a -> LBS.ByteString
format err = toMarkup err & renderMarkup
appToErr :: ServerError -> Text -> ServerError
appToErr x msg = x
{ errBody = toS $ format (Err (toS (errReasonPhrase x)) msg)
, errHeaders = [("Content-Type","text/html")]}
unauthorized :: (MonadError ServerError m) => Text -> m a
unauthorized = throwError . unauthorizedErr
unauthorizedErr :: Text -> ServerError
unauthorizedErr = appToErr err401
forbidden :: (MonadError ServerError m) => Text -> m a
forbidden = throwError . forbiddenErr
forbiddenErr :: Text -> ServerError
forbiddenErr = appToErr err403
notFound :: ( MonadError ServerError m) => Text -> m a
notFound = throwError . notFoundErr
notFoundErr :: Text -> ServerError
notFoundErr = appToErr err404
preconditionFailed :: ( MonadError ServerError m) => Text -> m a
preconditionFailed = throwError . preconditionFailedErr
preconditionFailedErr :: Text -> ServerError
preconditionFailedErr = appToErr err412
serverError :: ( MonadError ServerError m) => Text -> m a
serverError = throwError . serverErrorErr
serverErrorErr :: Text -> ServerError
serverErrorErr = appToErr err500
```

View file

@ -0,0 +1,250 @@
# Pagination
## Overview
Let's see an approach to typed pagination with *Servant* using [servant-pagination](https://hackage.haskell.org/package/servant-pagination).
This module offers opinionated helpers to declare a type-safe and a flexible pagination
mechanism for Servant APIs. This design, inspired by [Heroku's API](https://devcenter.heroku.com/articles/platform-api-reference#ranges),
provides a small framework to communicate about a possible pagination feature of an endpoint,
enabling a client to consume the API in different fashions (pagination with offset / limit,
endless scroll using last referenced resources, ascending and descending ordering, etc.)
Therefore, client can provide a `Range` header with their request with the following format:
- `Range: <field> [<value>][; offset <o>][; limit <l>][; order <asc|desc>]`
For example: `Range: createdAt 2017-01-15T23:14:67.000Z; offset 5; order desc` indicates that
the client is willing to retrieve the next batch of document in descending order that were
created after the fifteenth of January, skipping the first 5.
As a response, the server may return the list of corresponding documents, and augment the
response with 3 headers:
- `Accept-Ranges`: A comma-separated list of fields upon which a range can be defined
- `Content-Range`: Actual range corresponding to the content being returned
- `Next-Range`: Indicate what should be the next `Range` header in order to retrieve the next range
For example:
- `Accept-Ranges: createdAt, modifiedAt`
- `Content-Range: createdAt 2017-01-15T23:14:51.000Z..2017-02-18T06:10:23.000Z`
- `Next-Range: createdAt 2017-02-19T12:56:28.000Z; offset 0; limit 100; order desc`
## Getting Started
Code-wise the integration is quite seamless and unobtrusive. `servant-pagination` provides a
`Ranges (fields :: [Symbol]) (resource :: *) -> *` data-type for declaring available ranges
on a group of _fields_ and a target _resource_. To each combination (resource + field) is
associated a given type `RangeType (resource :: *) (field :: Symbol) -> *` as described by
the type-family in the `HasPagination` type-class.
So, let's start with some imports and extensions to get this out of the way:
``` haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
import Data.Aeson
(ToJSON, genericToJSON)
import Data.Maybe
(fromMaybe)
import Data.Proxy
(Proxy (..))
import GHC.Generics
(Generic)
import Servant
((:>), GetPartialContent, Handler, Header, Headers, JSON, Server, addHeader)
import Servant.Pagination
(HasPagination (..), PageHeaders, Range (..), Ranges, RangeOptions(..),
applyRange, extractRange, returnRange)
import qualified Data.Aeson as Aeson
import qualified Network.Wai.Handler.Warp as Warp
import qualified Servant
import qualified Servant.Pagination as Pagination
```
#### Declaring the Resource
Servant APIs are rather resource-oriented, and so is `servant-pagination`. This
guide shows a basic example working with `JSON` (as you could tell from the
import list already). To make the world a <span style='text-decoration:
line-through'>better</span> colored place, let's create an API to retrieve
colors -- with pagination.
``` haskell
data Color = Color
{ name :: String
, rgb :: [Int]
, hex :: String
} deriving (Eq, Show, Generic)
instance ToJSON Color where
toJSON =
genericToJSON Aeson.defaultOptions
colors :: [Color]
colors =
[ Color "Black" [0, 0, 0] "#000000"
, Color "Blue" [0, 0, 255] "#0000ff"
, Color "Green" [0, 128, 0] "#008000"
, Color "Grey" [128, 128, 128] "#808080"
, Color "Purple" [128, 0, 128] "#800080"
, Color "Red" [255, 0, 0] "#ff0000"
, Color "Yellow" [255, 255, 0] "#ffff00"
]
```
#### Declaring the Ranges
Now that we have defined our _resource_ (a.k.a `Color`), we are ready to declare a new `Range`
that will operate on a "name" field (genuinely named after the `name` fields from the `Color`
record).
For that, we need to tell `servant-pagination` two things:
- What is the type of the corresponding `Range` values
- How do we get one of these values from our resource
This is done via defining an instance of `HasPagination` as follows:
``` haskell
instance HasPagination Color "name" where
type RangeType Color "name" = String
getFieldValue _ = name
-- getRangeOptions :: Proxy "name" -> Proxy Color -> RangeOptions
-- getDefaultRange :: Proxy Color -> Range "name" String
defaultRange :: Range "name" String
defaultRange =
getDefaultRange (Proxy @Color)
```
Note that `getFieldValue :: Proxy "name" -> Color -> String` is the minimal complete definition
of the class. Yet, you can define `getRangeOptions` to provide different parsing options (see
the last section of this guide). In the meantime, we've also defined a `defaultRange` as it will
come in handy when defining our handler.
#### API
Good, we have a resource, we have a `Range` working on that resource, we can now declare our
API using other Servant combinators we already know:
``` haskell
type API =
"colors"
:> Header "Range" (Ranges '["name"] Color)
:> GetPartialContent '[JSON] (Headers MyHeaders [Color])
type MyHeaders =
Header "Total-Count" Int ': PageHeaders '["name"] Color
```
`PageHeaders` is a type alias provided by the library to declare the necessary response headers
we mentioned in introduction. Expanding the alias boils down to the following:
``` haskell
-- type MyHeaders =
-- '[ Header "Total-Count" Int
-- , Header "Accept-Ranges" (AcceptRanges '["name"])
-- , Header "Content-Range" (ContentRange '["name"] Color)
-- , Header "Next-Range" (Ranges '["name"] Color)
-- ]
```
As a result, we will need to provide all those headers with the response in our handler. Worry
not, _servant-pagination_ provides an easy way to lift a collection of resources into such handler.
#### Server
Time to connect the last bits by defining the server implementation of our colorful API. The `Ranges`
type we've defined above (tied to the `Range` HTTP header) indicates the server to parse any `Range`
header, looking for the format defined in introduction with fields and target types we have just declared.
If no such header is provided, we will end up receiving `Nothing`. Otherwise, it will be possible
to _extract_ a `Range` from our `Ranges`.
``` haskell
server :: Server API
server = handler
where
handler :: Maybe (Ranges '["name"] Color) -> Handler (Headers MyHeaders [Color])
handler mrange = do
let range =
fromMaybe defaultRange (mrange >>= extractRange)
addHeader (length colors) <$> returnRange range (applyRange range colors)
main :: IO ()
main =
Warp.run 1442 $ Servant.serve (Proxy @API) server
```
Let's try it out using different ranges to observe the server's behavior. As a reminder, here's
the format we defined, where `<field>` here can only be `name` and `<value>` must parse to a `String`:
- `Range: <field> [<value>][; offset <o>][; limit <l>][; order <asc|desc>]`
Beside the target field, everything is pretty much optional in the `Range` HTTP header. Missing parts
are deduced from the `RangeOptions` that are part of the `HasPagination` instance. Therefore, all
following examples are valid requests to send to our server:
- 1 - `curl http://localhost:1442/colors -vH 'Range: name'`
- 2 - `curl http://localhost:1442/colors -vH 'Range: name; limit 2'`
- 3 - `curl http://localhost:1442/colors -vH 'Range: name Green; order asc; offset 1'`
Considering the following default options:
- `defaultRangeLimit: 100`
- `defaultRangeOffset: 0`
- `defaultRangeOrder: RangeDesc`
The previous ranges reads as follows:
- 1 - The first 100 colors, ordered by descending names
- 2 - The first 2 colors, ordered by descending names
- 3 - The 100 colors after `Green` (not included), ordered by ascending names.
## Going Forward
#### Multiple Ranges
Note that in the simple above scenario, there's no ambiguity with `extractRange` and `returnRange`
because there's only one possible `Range` defined on our resource. Yet, as you've most probably
noticed, the `Ranges` combinator accepts a list of fields, each of which must declare a `HasPagination`
instance. Doing so will make the other helper functions more ambiguous and type annotations are
highly likely to be needed.
``` haskell
instance HasPagination Color "hex" where
type RangeType Color "hex" = String
getFieldValue _ = hex
-- to then define: Ranges '["name", "hex"] Color
```
#### Parsing Options
By default, `servant-pagination` provides an implementation of `getRangeOptions` for each
`HasPagination` instance. However, this can be overridden when defining the instance to provide
your own options. These options come into play when a `Range` header is received and isn't fully
specified (`limit`, `offset`, `order` are all optional) to provide default fallback values for those.
For instance, let's say we wanted to change the default limit to `5` in a new range on
`"rgb"`, we could tweak the corresponding `HasPagination` instance as follows:
``` haskell
instance HasPagination Color "rgb" where
type RangeType Color "rgb" = Int
getFieldValue _ = sum . rgb
getRangeOptions _ _ = Pagination.defaultOptions { defaultRangeLimit = 5 }
```

View file

@ -0,0 +1,23 @@
cabal-version: 2.2
name: cookbook-pagination
version: 2.1
synopsis: Pagination with Servant example
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-pagination
main-is: Pagination.lhs
build-tool-depends: markdown-unlit:markdown-unlit
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-depends: base >= 4.9 && <5
, aeson
, servant
, servant-server
, servant-pagination >= 2.1.0 && < 3.0.0
, warp

View file

@ -0,0 +1,116 @@
# Error logging with Sentry
In this recipe we will use [Sentry](https://sentry.io) to collect the runtime exceptions generated by our application. We will use the [raven-haskell](https://hackage.haskell.org/package/raven-haskell) package, which is a client for a Sentry event server. Mind that this package is not present on [Stackage](https://www.stackage.org/), so if we are using [Stack](https://docs.haskellstack.org) well need to add it to our `extra-deps` section in the `stack.yaml` file.
To exemplify this we will need the following imports
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
import Control.Exception (Exception,
SomeException, throw)
import Data.ByteString.Char8 (unpack)
import Network.Wai (Request, rawPathInfo,
requestHeaderHost)
import Network.Wai.Handler.Warp (defaultOnException,
defaultSettings,
runSettings,
setOnException,
setPort)
import Servant
import System.Log.Raven (initRaven, register,
silentFallback)
import System.Log.Raven.Transport.HttpConduit (sendRecord)
import System.Log.Raven.Types (SentryLevel (Error),
SentryRecord (..))
```
Just for the sake of the example we will use the following API which will throw an exception
```haskell
type API = "break" :> Get '[JSON] ()
data MyException = MyException deriving (Show)
instance Exception MyException
server = breakHandler
where breakHandler :: Handler ()
breakHandler = do
throw MyException
return ()
```
First thing we need to do if we want to intercept and log this exception, we need to look in the section of our code where we run the `warp` application, and instead of using the simple `run` function from `warp`, we use the `runSettings` functions which allows to customise the handling of requests
```haskell
main :: IO ()
main =
let
settings =
setPort 8080 $
setOnException sentryOnException $
defaultSettings
in
runSettings settings $ serve (Proxy :: Proxy API) server
```
The definition of the `sentryOnException` function could look as follows
```haskell
sentryOnException :: Maybe Request -> SomeException -> IO ()
sentryOnException mRequest exception = do
sentryService <- initRaven
"https://username:password@senty.host/id"
id
sendRecord
silentFallback
register
sentryService
"myLogger"
Error
(formatMessage mRequest exception)
(recordUpdate mRequest exception)
defaultOnException mRequest exception
```
It does three things. First it initializes the service which will communicate with Sentry. The parameters it receives are:
- the Sentry `DSN`, which is obtained when creating a new project on Sentry
- a default way to update sentry fields, where we use the identity function
- an event transport, which generally would be `sendRecord`, an HTTPS capable transport which uses http-conduit
- a fallback handler, which we choose to be `silentFallback` since later we are logging to the console anyway.
In the second step it actually sends our message to Sentry with the `register` function. Its arguments are:
- the configured Sentry service which we just created
- the name of the logger
- the error level (see [SentryLevel](https://hackage.haskell.org/package/raven-haskell/docs/System-Log-Raven-Types.html#t:SentryLevel) for the possible options)
- the message we want to send
- an update function to handle the specific `SentryRecord`
Eventually it just delegates the error handling to the default warp mechanism.
The function `formatMessage` simply uses the request and the exception to return a string with the error message.
```haskell
formatMessage :: Maybe Request -> SomeException -> String
formatMessage Nothing exception = "Exception before request could be parsed: " ++ show exception
formatMessage (Just request) exception = "Exception " ++ show exception ++ " while handling request " ++ show request
```
The only piece left now is the `recordUpdate` function which allows to decorate with other [attributes](https://docs.sentry.io/clientdev/attributes/) the default `SentryRecord`.
```haskell
recordUpdate :: Maybe Request -> SomeException -> SentryRecord -> SentryRecord
recordUpdate Nothing exception record = record
recordUpdate (Just request) exception record = record
{ srCulprit = Just $ unpack $ rawPathInfo request
, srServerName = fmap unpack $ requestHeaderHost request
}
```
In this examples we set the raw path as the culprit and we use the `Host` header to populate the server name field.
You can try to run this code using the `cookbook-sentry` executable. You should obtain a `MyException` error in the console and, if you provided a valid Sentry DSN, you should also find your error in the Sentry interface.

View file

@ -0,0 +1,24 @@
cabal-version: 2.2
name: cookbook-sentry
version: 0.1
synopsis: Collecting runtime exceptions using Sentry
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-sentry
main-is: Sentry.lhs
build-depends: base == 4.*
, bytestring
, markdown-unlit >= 0.4
, raven-haskell >= 0.1.2
, servant-server
, warp >= 3.2
, wai >= 3.2
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit

View file

@ -144,7 +144,7 @@ simpleAPIServer
:: m [a] :: m [a]
-> (i -> m a) -> (i -> m a)
-> (a -> m NoContent) -> (a -> m NoContent)
-> Server (SimpleAPI name a i) m -> ServerT (SimpleAPI name a i) m
simpleAPIServer listAs getA postA = simpleAPIServer listAs getA postA =
listAs :<|> getA :<|> postA listAs :<|> getA :<|> postA

View file

@ -1,14 +1,14 @@
cabal-version: 2.2
name: cookbook-structuring-apis name: cookbook-structuring-apis
version: 0.1 version: 0.1
synopsis: Example that shows how APIs can be structured synopsis: Example that shows how APIs can be structured
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-structuring-apis executable cookbook-structuring-apis
main-is: StructuringApis.lhs main-is: StructuringApis.lhs

View file

@ -0,0 +1,506 @@
# How To Test Servant Applications
Even with a nicely structured API that passes Haskell's strict type checker,
it's a good idea to write some tests for your application.
In this recipe we'll work through some common testing strategies and provide
examples of utlizing these testing strategies in order to test Servant
applications.
## Testing strategies
There are many testing strategies you may wish to employ when testing your
Servant application, but included below are three common testing patterns:
- We'll use `servant-client` to derive client functions and then send valid
requests to our API, running in another thread. This is great for testing
that our **business logic** is correctly implemented with only valid HTTP
requests.
- We'll also use `hspec-wai` to make **arbitrary HTTP requests**, in order to
test how our application may respond to invalid or otherwise unexpected
requests.
- Finally, we can also use `servant-quickcheck` for **whole-API tests**, in order
to assert that our entire application conforms to **best practices**.
## Useful Libraries
The following libraries will often come in handy when we decide to test our
Servant applications:
- [hspec](https://hspec.github.io/)
- [hspec-wai](http://hackage.haskell.org/package/hspec-wai)
- [QuickCheck](http://hackage.haskell.org/package/QuickCheck)
- [servant-quickcheck](https://hackage.haskell.org/package/servant-quickcheck)
## Imports and Our Testing Module
This recipe starts with the following ingredients:
```haskell
{-# LANGUAGE OverloadedStrings, TypeFamilies, DataKinds,
DeriveGeneric, TypeOperators #-}
import Prelude ()
import Prelude.Compat
import qualified Control.Concurrent as C
import Control.Concurrent.MVar
import Control.Exception (bracket)
import Control.Lens hiding (Context)
import Data.Aeson
import Data.Aeson.Lens
import qualified Data.HashMap.Strict as HM
import Data.Text (Text, unpack)
import GHC.Generics
import Network.HTTP.Client hiding (Proxy)
import Network.HTTP.Types
import Network.Wai
import qualified Network.Wai.Handler.Warp as Warp
import Servant
import Servant.Client
import Servant.Server
import Servant.QuickCheck
import Servant.QuickCheck.Internal (serverDoesntSatisfy)
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.Matcher
```
We're going to produce different `Spec`s that represent different
aspects of our application, and we'll ask `hspec` to run all of our different
`Spec`s. This is a common organizational method for testing modules:
```haskell
spec :: Spec
spec = do
businessLogicSpec
thirdPartyResourcesSpec
servantQuickcheckSpec
```
Often, codebases will use `hspec`'s
[autodiscover pragma](http://hspec.github.io/hspec-discover.html)
to find all testing modules and `Spec`s inside, but we're going to
explicitly make a `main` function to run our tests because we have only one
`spec` defined above:
```haskell
main :: IO ()
main = hspec spec
```
## Testing Your Business Logic
Let's say we have an API that looks something like this:
```haskell
data User = User {
name :: Text
, user_id :: Integer
} deriving (Eq, Show, Generic)
instance FromJSON User
instance ToJSON User
type UserApi =
-- One endpoint: create a user
"user" :> Capture "userId" Integer :> Post '[JSON] User
```
A real server would likely use a database to store, retrieve, and validate
users, but we're going to do something really simple merely to have something
to test. With that said, here's a sample handler, server, and `Application`
for the endpoint described above:
```haskell
userApp :: Application
userApp = serve (Proxy :: Proxy UserApi) userServer
userServer :: Server UserApi
userServer = createUser
createUser :: Integer -> Handler User
createUser userId = do
if userId > 5000
then pure $ User { name = "some user", user_id = userId }
else throwError $ err400 { errBody = "userId is too small" }
```
### Strategy 1: Spin Up a Server, Create a Client, Make Some Requests
One of the benefits of Servant's type-level DSL for describing APIs is that
once you have provided a type-level description of your API, you can create
clients, documentation, or other tools for it somewhat magically.
In this case, we'd like to *test* our server, so we can use `servant-client`
to create a client, after which we'll run our server, and then make requests
of it and see how it responds.
Let's write some tests:
```haskell
withUserApp :: (Warp.Port -> IO ()) -> IO ()
withUserApp action =
-- testWithApplication makes sure the action is executed after the server has
-- started and is being properly shutdown.
Warp.testWithApplication (pure userApp) action
businessLogicSpec :: Spec
businessLogicSpec =
-- `around` will start our Server before the tests and turn it off after
around withUserApp $ do
-- create a test client function
let createUser = client (Proxy :: Proxy UserApi)
-- create a servant-client ClientEnv
baseUrl <- runIO $ parseBaseUrl "http://localhost"
manager <- runIO $ newManager defaultManagerSettings
let clientEnv port = mkClientEnv manager (baseUrl { baseUrlPort = port })
-- testing scenarios start here
describe "POST /user" $ do
it "should create a user with a high enough ID" $ \port -> do
result <- runClientM (createUser 50001) (clientEnv port)
result `shouldBe` (Right $ User { name = "some user", user_id = 50001})
it "will it fail with a too-small ID?" $ \port -> do
result <- runClientM (createUser 4999) (clientEnv port)
result `shouldBe` (Right $ User { name = "some user", user_id = 50001})
```
### Running These Tests
Let's run our tests and see what happens:
```
$ cabal new-test all
POST /user
should create a user with a high enough ID
should fail with a too-small ID FAILED [1]
Failures:
Testing.lhs:129:7:
1) POST /user should fail with a too-small ID
expected: Right (User {name = "some user", user_id = 50001})
but got: Left (FailureResponse (Response {responseStatusCode = Status {statusCode = 400, statusMessage = "Bad Request"}, responseHeaders = fromList [("Transfer-Encoding","chunked"),("Date","Fri, 12 Oct 2018 04:36:22 GMT"),("Server","Warp/3.2.25")], responseHttpVersion = HTTP/1.1, responseBody = "userId is too small"}))
To rerun use: --match "/POST /user/should fail with a too-small ID/"
```
Hmm. One passed and one failed! It looks like I *was* expecting a success
response in the second test, but I actually got a failure. We should fix that,
but first I'd like to introduce `hspec-wai`, which will give us different
mechanisms for making requests of our application and validating the responses
we get. We're also going to spin up a fake Elasticsearch server, so that our
server can think it's talking to a real database.
## *Mocking* 3rd Party Resources
Often our web applications will need to make their own web
requests to other 3rd-party applications. These requests provide a lot
of opportunity for failure and so we'd like to test that the right
messages and failure values (in addition to success values) are returned
from our application.
### Define the 3rd-Party Resource
With Servant's type-level API definitions, assuming you've already defined the
API you want to mock, it's relatively trivial to create a simple server for
the purposes of running tests. For instance, consider an API server that needs
to get data out of Elasticsearch. Let's first define the Elasticsearch server
and client using Servant API descriptions:
```haskell
type SearchAPI =
-- We're using Aeson's Generic JSON `Value` to make things easier on
-- ourselves. We're also representing only one Elasticsearch endpoint:
-- get item by id
"myIndex" :> "myDocType" :> Capture "docId" Integer :> Get '[JSON] Value
-- Here's our Servant Client function
getDocument = client (Proxy :: Proxy SearchAPI)
-- We can use these helpers when we want to make requests
-- using our client function
clientEnv :: Text -> Text -> IO ClientEnv
clientEnv esHost esPort = do
baseUrl <- parseBaseUrl $ unpack $ esHost <> ":" <> esPort
manager <- newManager defaultManagerSettings
pure $ mkClientEnv manager baseUrl
runSearchClient :: Text -> Text -> ClientM a -> IO (Either ClientError a)
runSearchClient esHost esPort = (clientEnv esHost esPort >>=) . runClientM
```
### Servant Server Example Using this 3rd-Party Resource
So we've got an Elasticsearch server and a client to talk to it. Let's now
build a simple app server that uses this client to retrieve documents. This
is somewhat contrived, but hopefully it illustrates the typical three-tier
application architecture.
One note: we're also going to take advantage of `lens-aeson` here, which may
look a bit foreign. The gist of it is that we're going to traverse a JSON
`Value` from Elasticsearch and try to extract some kind of document to
return.
Imagine, then, that this is our real server implementation:
```haskell
type DocApi =
"docs" :> Capture "docId" Integer :> Get '[JSON] Value
docsApp :: Text -> Text -> Application
docsApp esHost esPort = serve (Proxy :: Proxy DocApi) $ docServer esHost esPort
docServer :: Text -> Text -> Server DocApi
docServer esHost esPort = getDocById esHost esPort
-- Our Handler tries to get a doc from Elasticsearch and then tries to parse
-- it. Unfortunately, there's a lot of opportunity for failure in these
-- actions
getDocById :: Text -> Text -> Integer -> Handler Value
getDocById esHost esPort docId = do
-- Our Servant Client function returns Either ClientError Value here:
docRes <- liftIO $ runSearchClient esHost esPort (getDocument docId)
case docRes of
Left err -> throwError $ err404 { errBody = "Failed looking up content" }
Right value -> do
-- we'll either fail to parse our document or we'll return it
case value ^? _Object . ix "_source" of
Nothing -> throwError $ err400 { errBody = "Failed parsing content" }
Just obj -> pure obj
```
### Testing Our Backend
So the above represents our application and is close to a server we may
actually deploy. How then shall we test this application?
Ideally, we'd like it to make requests of a *real* Elasticsearch server, but
we certainly don't want our tests to trigger requests to a live, production
database. In addition, we don't want to depend on our real Elasticsearch
server having specific, consistent results for us to test against, because
that would make our tests flaky (and flaky tests are sometimes described as
worse than not having tests at all).
One solution to this is to create a trivial Elasticsearch server as part of
our testing code. We can do this relatively easily because we already have
an API definition for it above. With a *real* server, we can then let our own
application make requests of it and we'll simulate different scenarios in
order to make sure our application responds the way we expect it to.
Let's start with some helpers which will allow us to run a testing version
of our Elasticsearch server in another thread:
```haskell
-- | We'll run the Elasticsearch server so we can test behaviors
withElasticsearch :: IO () -> IO ()
withElasticsearch action =
bracket (liftIO $ C.forkIO $ Warp.run 9999 esTestApp)
C.killThread
(const action)
esTestApp :: Application
esTestApp = serve (Proxy :: Proxy SearchAPI) esTestServer
esTestServer :: Server SearchAPI
esTestServer = getESDocument
-- This is the *mock* handler we're going to use. We create it
-- here specifically to trigger different behavior in our tests.
getESDocument :: Integer -> Handler Value
getESDocument docId
-- arbitrary things we can use in our tests to simulate failure:
-- we want to trigger different code paths.
| docId > 1000 = throwError err500
| docId > 500 = pure . Object $ HM.fromList [("bad", String "data")]
| otherwise = pure $ Object $ HM.fromList [("_source", Object $ HM.fromList [("a", String "b")])]
```
Now, we should be ready to write some tests.
In this case, we're going to use `hspec-wai`, which will give us a simple way
to run our application, make requests, and make assertions against the
responses we receive.
Hopefully, this will simplify our testing code:
```haskell
thirdPartyResourcesSpec :: Spec
thirdPartyResourcesSpec = around_ withElasticsearch $ do
-- we call `with` from `hspec-wai` and pass *real* `Application`
with (pure $ docsApp "localhost" "9999") $ do
describe "GET /docs" $ do
it "should be able to get a document" $
-- `get` is a function from hspec-wai`.
get "/docs/1" `shouldRespondWith` 200
it "should be able to handle connection failures" $
get "/docs/1001" `shouldRespondWith` 404
it "should be able to handle parsing failures" $
get "/docs/501" `shouldRespondWith` 400
it "should be able to handle odd HTTP requests" $
-- we can also make all kinds of arbitrary custom requests to see how
-- our server responds using the `request` function:
-- request :: Method -> ByteString -> [Header]
-- -> LB.ByteString -> WaiSession SResponse
request methodPost "/docs/501" [] "{" `shouldRespondWith` 405
it "we can also do more with the Response using hspec-wai's matchers" $
-- see also `MatchHeader` and JSON-matching tools as well...
get "/docs/1" `shouldRespondWith` 200 { matchBody = MatchBody bodyMatcher }
bodyMatcher :: [Network.HTTP.Types.Header] -> Body -> Maybe String
bodyMatcher _ body = case (decode body :: Maybe Value) of
-- success in this case means we return `Nothing`
Just val | val == (Object $ HM.fromList [("a", String "b")]) -> Nothing
_ -> Just "This is how we represent failure: this message will be printed"
```
Out of the box, `hspec-wai` provides a lot of useful tools for us to run tests
against our application. What happens when we run these tests?
```
$ cabal new-test all
...
GET /docs
should be able to get a document
should be able to handle connection failures
should be able to handle parsing failures
should be able to handle odd HTTP requests
we can also do more with the Response using hspec-wai's matchers
```
Fortunately, they all passed! Let's move to another strategy: whole-API
testing.
## Servant Quickcheck
[`servant-quickcheck`](https://github.com/haskell-servant/servant-quickcheck)
is a project that allows users to write tests for whole Servant APIs using
quickcheck-style property-checking mechanisms.
`servant-quickcheck` is great for asserting API-wide rules, such as "no
endpoint throws a 500" or "all 301 status codes also come with a Location
header". The project even comes with a number of predicates that reference
the [RFCs they originate from](https://github.com/haskell-servant/servant-quickcheck/blob/master/src/Servant/QuickCheck/Internal/Predicates.hs).
In other words, it's one way to assert that your APIs conform to specs and
best practices.
### Quickcheckable API
Let's make an API and a server to demonstrate how to use `servant-quickcheck`:
```haskell
type API = ReqBody '[JSON] String :> Post '[JSON] String
:<|> Get '[JSON] Int
:<|> BasicAuth "some-realm" () :> Get '[JSON] ()
api :: Proxy API
api = Proxy
server :: IO (Server API)
server = do
mvar <- newMVar ""
return $ (\x -> liftIO $ swapMVar mvar x)
:<|> (liftIO $ readMVar mvar >>= return . length)
:<|> (const $ return ())
```
### Using `servant-quickcheck`
Let's build some tests for our API using `servant-quickcheck`.
Similar to the above examples, we're going to create `Spec`s, but in this
case, we'll rely on a number of predicates available from `servant-quickcheck`
to see if our API server conforms to best practices:
```haskell
-- Let's set some QuickCheck values
args :: Args
args = defaultArgs { maxSuccess = 500 }
-- Here's a Servant Context object we'll use
ctx :: Context '[BasicAuthCheck ()]
ctx = BasicAuthCheck (const . return $ NoSuchUser) :. EmptyContext
servantQuickcheckSpec :: Spec
servantQuickcheckSpec = describe "" $ do
it "API demonstrates best practices" $
-- `withServerServer` and `withServantServerAndContext` come from `servant-quickcheck`
withServantServerAndContext api ctx server $ \burl ->
-- `serverSatisfies` and the predicates also come from `servant-quickcheck`
serverSatisfies api burl args (unauthorizedContainsWWWAuthenticate
<%> not500
<%> onlyJsonObjects -- this one isn't true!
<%> mempty)
it "API doesn't have these things implemented yet" $
withServantServerAndContext api ctx server $ \burl -> do
serverDoesntSatisfy api burl args (getsHaveCacheControlHeader
<%> notAllowedContainsAllowHeader
<%> mempty)
```
Let's see what happens when we run these tests:
```
API demonstrates best practices FAILED [2]
+++ OK, passed 500 tests.
API doesn't have these things implemented yet
src/Servant/QuickCheck/Internal/QuickCheck.hs:143:11:
2) Main[339:25] API demonstrates best practices
Failed:
Just Predicate failed
Predicate: onlyJsonObjects
Response:
Status code: 200
Headers: "Transfer-Encoding": "chunked"
"Date": "Fri, 12 Oct 2018 04:36:22 GMT"
"Server": "Warp/3.2.25"
"Content-Type": "application/json;charset=utf-8"
Body: ""
To rerun use: --match "/Main[339:25]/API demonstrates best practices/"
Randomized with seed 1046277487
Finished in 0.4306 seconds
```
Hmm. It looks like we *thought* our API only returned JSON objects, which is a
best practice, but in fact, we *did* have an endpoint that returned an empty
body, which you can see in the printed response above: `Body: ""`. We should
consider revising our API to only return top-level JSON Objects in the future!
### Other Cool Things
`servant-quickcheck` also has a cool mechanism where you can compare two API
servers to demonstrate that they respond identically to requests. This may be
useful if you are planning to rewrite one API in another language or with
another web framework. You have to specify whether you're looking for
`jsonEquality` vs regular `ByteString` equality, though.
## Conclusion
There are lots of techniques for testing and we only covered a few here.
Useful libraries such as `hspec-wai` have ways of running Wai `Application`s
and sending requests to them, while Servant's type-level DSL for defining APIs
allows us to more easily mock out servers and to derive clients, which will
only craft valid requests.
Lastly, if you want a broad overview of where your application fits in with
regard to best practices, consider using `servant-quickcheck`.
This program is available as a cabal project
[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/testing).

View file

@ -0,0 +1,38 @@
cabal-version: 2.2
name: cookbook-testing
version: 0.0.1
synopsis: Common testing patterns in Servant apps
description: This recipe includes various strategies for writing tests for Servant.
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
category: Servant
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-testing
main-is: Testing.lhs
build-depends: base == 4.*
, base-compat
, text >= 1.2
, aeson >= 1.2
, lens-aeson
, lens
, servant
, servant-client
, servant-server
, servant-quickcheck >= 0.0.10
, http-client
, http-types >= 0.12
, hspec
, hspec-wai
, QuickCheck
, unordered-containers
, warp >= 3.2
, wai >= 3.2
, wai-extra
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit

View file

@ -1,6 +1,6 @@
# Using a custom monad # Using a custom monad
In this section we will create and API for a book shelf without any backing DB storage. In this section we will create an API for a book shelf without any backing DB storage.
We will keep state in memory and share it between requests using `Reader` monad and `STM`. We will keep state in memory and share it between requests using `Reader` monad and `STM`.
We start with a pretty standard set of imports and definition of the model: We start with a pretty standard set of imports and definition of the model:
@ -115,3 +115,6 @@ Running cookbook-using-custom-monad...
[Book "To Kill a Mockingbird",Book "Harry Potter and the Order of the Phoenix"] [Book "To Kill a Mockingbird",Book "Harry Potter and the Order of the Phoenix"]
[Book "The Picture of Dorian Gray",Book "To Kill a Mockingbird",Book "Harry Potter and the Order of the Phoenix"] [Book "The Picture of Dorian Gray",Book "To Kill a Mockingbird",Book "Harry Potter and the Order of the Phoenix"]
``` ```
To use `Raw` endpoints, look at the
[servant-rawm](http://hackage.haskell.org/package/servant-rawm) package.

View file

@ -1,14 +1,14 @@
cabal-version: 2.2
name: cookbook-using-custom-monad name: cookbook-using-custom-monad
version: 0.1 version: 0.1
synopsis: Using custom monad to pass a state between handlers synopsis: Using custom monad to pass a state between handlers
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
license: BSD3 license: BSD-3-Clause
license-file: ../../../servant/LICENSE license-file: ../../../servant/LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10 tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
executable cookbook-using-custom-monad executable cookbook-using-custom-monad
main-is: UsingCustomMonad.lhs main-is: UsingCustomMonad.lhs

View file

@ -0,0 +1,192 @@
# Inspecting, debugging, simulating clients and more
or simply put: _a practical introduction to `Servant.Client.Free`_.
Someone asked on IRC how one could access the intermediate Requests (resp. Responses)
produced (resp. received) by client functions derived using servant-client.
My response to such inquiries is: to extend `servant-client` in an ad-hoc way (e.g for testing or debugging
purposes), use `Servant.Client.Free`. This recipe shows how.
First the module header, but this time We'll comment the imports.
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
module Main (main) where
```
We will primarily use `Servant.Client.Free`, it doesn't re-export anything
from `free` package, so we need to import it as well.
```haskell
import Control.Monad.Free
import Servant.Client.Free
```
Also we'll use `servant-client` internals, which uses `http-client`,
so let's import them *qualified*
```haskell
import qualified Servant.Client.Internal.HttpClient as I
import qualified Network.HTTP.Client as HTTP
```
The rest of the imports are for a server we implement here for completeness.
```haskell
import Servant
import Network.Wai.Handler.Warp (run)
import System.Environment (getArgs)
```
## API & Main
We'll work with a very simple API:
```haskell
type API = "square" :> Capture "n" Int :> Get '[JSON] Int
api :: Proxy API
api = Proxy
```
Next we implement a `main`. If passed `"server"` it will run `server`, if passed
`"client"` it will run a `test` function (to be defined next). This should be
pretty straightforward:
```haskell
main :: IO ()
main = do
args <- getArgs
case args of
("server":_) -> do
putStrLn "Starting cookbook-using-free-client at http://localhost:8000"
run 8000 $ serve api $ \n -> return (n * n)
("client":_) ->
test
_ -> do
putStrLn "Try:"
putStrLn "cabal new-run cookbook-using-free-client server"
putStrLn "cabal new-run cookbook-using-free-client client"
```
## Test
In the client part, we will use a `Servant.Client.Free` client.
Because we have a single endpoint API, we'll get a single client function,
running in the `Free ClientF` (free) monad:
```haskell
getSquare :: Int -> Free ClientF Int
getSquare = client api
```
Such clients are "client functions without a backend", so to speak,
or where the backend has been abstracted out. To be more precise, `ClientF` is a functor that
precisely represents the operations servant-client-core needs from an http client backend.
So if we are to emulate one or augment what such a backend does, it will be by interpreting
all those operations, the way we want to. This also means we get access to the requests and
responses and can do anything we want with them, right when they are produced or consumed,
respectively.
Next, we can write our small test. We'll pass a value to `getSquare` and inspect
the `Free` structure. The first three possibilities are self-explanatory:
```haskell
test :: IO ()
test = case getSquare 42 of
Pure n ->
putStrLn $ "ERROR: got pure result: " ++ show n
Free (Throw err) ->
putStrLn $ "ERROR: got error right away: " ++ show err
```
We are interested in `RunRequest`, that's what client should block on:
```haskell
Free (RunRequest req k) -> do
```
Then we need to prepare the context, get HTTP (connection) `Manager`
and `BaseUrl`:
```haskell
burl <- parseBaseUrl "http://localhost:8000"
mgr <- HTTP.newManager HTTP.defaultManagerSettings
```
Now we can use `servant-client`'s internals to convert servant's `Request`
to http-client's `Request`, and we can inspect it:
```haskell
req' <- I.defaultMakeClientRequest burl req
putStrLn $ "Making request: " ++ show req'
```
`servant-client`'s request does a little more, but this is good enough for
our demo. We get back an http-client `Response` which we can also inspect.
```haskell
res' <- HTTP.httpLbs req' mgr
putStrLn $ "Got response: " ++ show res'
```
And we continue by turning http-client's `Response` into servant's `Response`,
and calling the continuation. We should get a `Pure` value.
```haskell
let res = I.clientResponseToResponse id res'
case k res of
Pure n ->
putStrLn $ "Expected 1764, got " ++ show n
_ ->
putStrLn "ERROR: didn't get a response"
```
So that's it. Using `Free` we can evaluate servant clients step-by-step, and
validate that the client functions or the HTTP client backend does what we expect
(e.g by printing requests/responses on the fly). In fact, using `Servant.Client.Free`
is a little simpler than defining a custom `RunClient` instance, especially
for those cases where it is handy to have the full sequence of client calls
and responses available for us to inspect, since `RunClient` only gives us
access to one `Request` or `Response` at a time.
On the other hand, a "batch collection" of requests and/or responses can be achieved
with both free clients and a custom `RunClient` instance rather easily, for example
by using a `Writer [(Request, Response)]` monad.
Here is an example of running our small `test` against a running server:
```
Making request: Request {
host = "localhost"
port = 8000
secure = False
requestHeaders = [("Accept","application/json;charset=utf-8,application/json")]
path = "/square/42"
queryString = ""
method = "GET"
proxy = Nothing
rawBody = False
redirectCount = 10
responseTimeout = ResponseTimeoutDefault
requestVersion = HTTP/1.1
}
Got response: Response
{ responseStatus = Status {statusCode = 200, statusMessage = "OK"}
, responseVersion = HTTP/1.1
, responseHeaders =
[ ("Transfer-Encoding","chunked")
, ("Date","Thu, 05 Jul 2018 21:12:41 GMT")
, ("Server","Warp/3.2.22")
, ("Content-Type","application/json;charset=utf-8")
]
, responseBody = "1764"
, responseCookieJar = CJ {expose = []}
, responseClose' = ResponseClose
}
Expected 1764, got 1764
```

View file

@ -0,0 +1,26 @@
cabal-version: 2.2
name: cookbook-using-free-client
version: 0.1
synopsis: Using Free client
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
executable cookbook-using-free-client
main-is: UsingFreeClient.lhs
build-depends: base >= 4.9 && <5
, free
, servant
, servant-client
, http-client
, servant-client-core
, base-compat
, servant-server
, warp >= 3.2
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4

View file

@ -0,0 +1,223 @@
# Listing alternative responses and exceptions in your API types
Servant allows you to talk about the exceptions you throw in your API
types. This is not limited to actual exceptions, you can write
handlers that respond with arbitrary open unions of types.
## Compatibility
:warning: This cookbook is compatible with GHC 8.6.1 or higher :warning:
## Preliminaries
```haskell
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wall -Wno-orphans #-}
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (async)
import Control.Monad (when)
import Control.Monad.Except (ExceptT (..), MonadError (..), MonadTrans (..), runExceptT)
import Data.Aeson (FromJSON (..), ToJSON (..))
import Data.Aeson.Encode.Pretty (encodePretty)
import Data.String.Conversions (cs)
import Data.Swagger (ToSchema)
import Data.Typeable (Proxy (Proxy))
import qualified GHC.Generics as GHC
import qualified Network.HTTP.Client as Client
import qualified Network.Wai.Handler.Warp as Warp
import Servant.API
import Servant.Client
import Servant.Server
import Servant.Swagger
```
## The API
This looks like a `Verb`-based routing table, except that `UVerb` has
no status, and carries a list of response types rather than a single
one. Each entry in the list carries its own response code.
```haskell
type API =
"fisx" :> Capture "bool" Bool
:> UVerb 'GET '[JSON] '[FisxUser, WithStatus 303 String]
:<|> "arian"
:> UVerb 'GET '[JSON] '[WithStatus 201 ArianUser]
```
Here are the details:
```haskell
data FisxUser = FisxUser {name :: String}
deriving (Eq, Show, GHC.Generic)
instance ToJSON FisxUser
instance FromJSON FisxUser
instance ToSchema FisxUser
-- | 'HasStatus' allows us to can get around 'WithStatus' if we want
-- to, and associate the status code with our resource types directly.
--
-- (To avoid orphan instances and make it more explicit what's in the
-- API and what isn't, we could even introduce a newtype 'Resource'
-- that wraps all the types we're using in our routing table, and then
-- define lots of 'HasStatus' instances for @Resource This@ and
-- @Resource That@.)
instance HasStatus FisxUser where
type StatusOf FisxUser = 203
data ArianUser = ArianUser
deriving (Eq, Show, GHC.Generic)
instance ToJSON ArianUser
instance FromJSON ArianUser
instance ToSchema ArianUser
```
## Server, Client, Swagger
You can just respond with any of the elements of the union in handlers.
```haskell
fisx :: Bool -> Handler (Union '[FisxUser, WithStatus 303 String])
fisx True = respond (FisxUser "fisx")
fisx False = respond (WithStatus @303 ("still fisx" :: String))
arian :: Handler (Union '[WithStatus 201 ArianUser])
arian = respond (WithStatus @201 ArianUser)
```
You can create client functions like you're used to:
```
fisxClient :: Bool -> ClientM (Union '[FisxUser, WithStatus 303 String])
arianClient :: ClientM (Union '[WithStatus 201 ArianUser])
(fisxClient :<|> arianClient) = client (Proxy @API)
```
... and that's basically it! Here are a few sample commands that
show you how the swagger docs look like and how you can handle the
result unions in clients:
```
main :: IO ()
main = do
putStrLn . cs . encodePretty $ toSwagger (Proxy @API)
_ <- async . Warp.run 8080 $ serve (Proxy @API) (fisx :<|> arian)
threadDelay 50000
mgr <- Client.newManager Client.defaultManagerSettings
let cenv = mkClientEnv mgr (BaseUrl Http "localhost" 8080 "")
result <- runClientM (fisxClient True) cenv
print $ foldMapUnion (Proxy @Show) show <$> result
print $ matchUnion @FisxUser <$> result
print $ matchUnion @(WithStatus 303 String) <$> result
pure ()
```
## Idiomatic exceptions
Since `UVerb` (probably) will mostly be used for error-like responses, it may be desirable to be able to early abort handler, like with current servant one would use `throwError` with `ServerError`.
```haskell
newtype UVerbT xs m a = UVerbT { unUVerbT :: ExceptT (Union xs) m a }
deriving (Functor, Applicative, Monad, MonadTrans)
-- | Deliberately hide 'ExceptT's 'MonadError' instance to be able to use
-- underlying monad's instance.
instance MonadError e m => MonadError e (UVerbT xs m) where
throwError = lift . throwError
catchError (UVerbT act) h = UVerbT $ ExceptT $
runExceptT act `catchError` (runExceptT . unUVerbT . h)
-- | This combinator runs 'UVerbT'. It applies 'respond' internally, so the handler
-- may use the usual 'return'.
runUVerbT :: (Monad m, HasStatus x, IsMember x xs) => UVerbT xs m x -> m (Union xs)
runUVerbT (UVerbT act) = either id id <$> runExceptT (act >>= respond)
-- | Short-circuit 'UVerbT' computation returning one of the response types.
throwUVerb :: (Monad m, HasStatus x, IsMember x xs) => x -> UVerbT xs m a
throwUVerb = UVerbT . ExceptT . fmap Left . respond
```
Example usage:
```haskell
data Foo = Foo Int Int Int
deriving (Show, Eq, GHC.Generic, ToJSON)
deriving HasStatus via WithStatus 200 Foo
data Bar = Bar
deriving (Show, Eq, GHC.Generic)
instance ToJSON Bar
h :: Handler (Union '[Foo, WithStatus 400 Bar])
h = runUVerbT $ do
when ({- something bad -} True) $
throwUVerb $ WithStatus @400 Bar
when ({- really bad -} False) $
throwError $ err500
-- a lot of code here...
return $ Foo 1 2 3
```
## Related Work
There is the [issue from
2017](https://github.com/haskell-servant/servant/issues/841) that was
resolved by the introduction of `UVerb`, with a long discussion on
alternative designs.
[servant-checked-exceptions](https://hackage.haskell.org/package/servant-checked-exceptions)
is a good solution to the problem, but it restricts the user to JSON
and a very specific envelop encoding for the union type, which is
often not acceptable. (One good reason for this design choice is that
it makes writing clients easier, where you need to get to the union
type from one representative, and you don't want to run several
parsers in the hope that the ones that should will always error out so
you can try until the right one returns a value.)
[servant-exceptions](https://github.com/ch1bo/servant-exceptions) is
another shot at the problem. It is inspired by
servant-checked-exceptions, so it may be worth taking a closer look.
The README claims that
[cardano-sl](https://github.com/input-output-hk/cardano-sl) also has
some code for generalized error handling.
In an earier version of the `UVerb` implementation, we have used some
code from
[world-peace](https://hackage.haskell.org/package/world-peace), but
that package itself wasn't flexible enough, and we had to use
[sop-core](https://hackage.haskell.org/package/sop-core) to implement
the `HasServer` instance.
Here is a blog post we found on the subject:
https://lukwagoallan.com/posts/unifying-servant-server-error-responses
(If you have anything else, please add it here or let us know.)
```haskell
main :: IO ()
main = return ()
```

View file

@ -0,0 +1,35 @@
cabal-version: 2.2
name: cookbook-uverb
version: 0.0.1
synopsis: How to use the 'UVerb' type.
description: Listing alternative responses and exceptions in your API types.
homepage: http://docs.servant.dev/
license: BSD-3-Clause
license-file: ../../../servant/LICENSE
author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com
category: Servant
build-type: Simple
tested-with: GHC==8.6.5, GHC==8.8.4, GHC==8.10.7
executable cookbook-uverb
main-is: UVerb.lhs
build-depends: base == 4.*
, aeson >= 1.2
, aeson-pretty >= 0.8.8
, async
, http-client
, mtl
, servant
, servant-client
, servant-server
, servant-swagger
, string-conversions
, swagger2
, wai
, warp
if impl(ghc >= 9)
buildable: False
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit

View file

@ -6,6 +6,11 @@
including a test-suite using [**hspec**](http://hspec.github.io/) and including a test-suite using [**hspec**](http://hspec.github.io/) and
**servant-client**. **servant-client**.
- **[servant-examples](https://github.com/sras/servant-examples)**:
Similar to [the cookbook](https://docs.servant.dev/en/latest/cookbook/index.html) but
with no explanations, for developers who just want to look at code examples to find out how to do X or Y
with servant.
- **[stack-templates](https://github.com/commercialhaskell/stack-templates)** - **[stack-templates](https://github.com/commercialhaskell/stack-templates)**
@ -32,7 +37,7 @@
An example consisting of An example consisting of
- a backend in uses `haskell-servant` - a backend that uses `servant`
- a frontend written in [PureScript](http://www.purescript.org/) using - a frontend written in [PureScript](http://www.purescript.org/) using
[servant-purescript](https://github.com/eskimor/servant-purescript) to generate [servant-purescript](https://github.com/eskimor/servant-purescript) to generate
an API wrapper in PureScript to interface the web API with an API wrapper in PureScript to interface the web API with
@ -43,3 +48,13 @@
An example for a web server written with **servant-server** and An example for a web server written with **servant-server** and
[persistent](https://www.stackage.org/package/persistent) for writing data [persistent](https://www.stackage.org/package/persistent) for writing data
into a database. into a database.
- **[full-example-servant-elm-auth-yeshql-postgresql](https://github.com/aRkadeFR/FlashCard)**:
A full open source website written with **servant-server**, yeshql, postgresql and elm 0.19.
- [`import Servant` github search](https://github.com/search?q=%22import+Servant%22+language%3AHaskell&type=Code)
It has thousands of results and can be a good way to see how people use servant in their projects or even to discover
servant-related libraries.

View file

@ -3,22 +3,24 @@ servant A Type-Level Web DSL
.. image:: https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png .. image:: https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png
**servant** is a set of packages for declaring web APIs at the type-level and **servant** is a set of Haskell libraries for writing *type-safe* web
then using those API specifications to: applications but also *deriving* clients (in Haskell and other languages) or
generating documentation for them, and more.
- write servers (this part of **servant** can be considered a web framework), This is achieved by taking as input a description of the web API
- obtain client functions (in haskell), as a Haskell type. Servant is then able to check that your server-side request
- generate client functions for other programming languages, handlers indeed implement your web API faithfully, or to automatically derive
- generate documentation for your web applications Haskell functions that can hit a web application that implements this API,
- and more... generate a Swagger description or code for client functions in some other
languages directly.
All in a type-safe manner. If you would like to learn more, click the tutorial link below.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
introduction.rst
tutorial/index.rst tutorial/index.rst
cookbook/index.rst cookbook/index.rst
examples.md examples.md
links.rst links.rst
principles.rst

View file

@ -3,7 +3,7 @@ Helpful Links
------------- -------------
- the central documentation (this site): - the central documentation (this site):
`haskell-servant.readthedocs.org <http://haskell-servant.readthedocs.org/>`_ `docs.servant.dev <http://docs.servant.dev/>`_
- the github repo: - the github repo:
`github.com/haskell-servant/servant <https://github.com/haskell-servant/servant>`_ `github.com/haskell-servant/servant <https://github.com/haskell-servant/servant>`_
@ -12,13 +12,13 @@ Helpful Links
`https://github.com/haskell-servant/servant/issues <https://github.com/haskell-servant/servant/issues>`_ `https://github.com/haskell-servant/servant/issues <https://github.com/haskell-servant/servant/issues>`_
- the irc channel: - the irc channel:
``#servant`` on freenode `#haskell-servant on libera.chat <https://web.libera.chat/#haskell-servant>`_
- the mailing list: - the mailing list:
`groups.google.com/forum/#!forum/haskell-servant <https://groups.google.com/forum/#!forum/haskell-servant>`_ `groups.google.com/forum/#!forum/haskell-servant <https://groups.google.com/forum/#!forum/haskell-servant>`_
- blog posts and videos and slides of some talks on servant: - blog posts and videos and slides of some talks on servant:
`haskell-servant.github.io <http://haskell-servant.github.io>`_ `www.servant.dev <http://www.servant.dev>`_
- the servant packages on hackage: - the servant packages on hackage:

View file

@ -1,5 +1,5 @@
Introduction Principles
------------ ----------
**servant** has the following guiding principles: **servant** has the following guiding principles:

View file

@ -1,25 +1,4 @@
alabaster==0.7.7 recommonmark==0.5.0
argh==0.26.1 Sphinx==1.8.4
Babel==2.2.0 sphinx_rtd_theme>=0.4.2
backports-abc==0.4 jinja2<3.1.0
backports.ssl-match-hostname==3.5.0.1
certifi==2015.11.20.1
CommonMark==0.5.4
docutils==0.12
Jinja2==2.8
livereload==2.4.1
MarkupSafe==0.23
pathtools==0.1.2
Pygments==2.2.0
pytz==2015.7
PyYAML==3.11
recommonmark==0.4.0
singledispatch==3.4.0.3
six==1.10.0
snowballstemmer==1.2.1
Sphinx==1.3.6
sphinx-autobuild==0.5.2
sphinx-rtd-theme==0.1.9
tornado==4.3
watchdog==0.8.3
wheel==0.26.0

View file

@ -129,16 +129,23 @@ type UserAPI4 = "users" :> Get '[JSON] [User]
### `StreamGet` and `StreamPost` ### `StreamGet` and `StreamPost`
*Note*: Streaming has changed considerably in `servant-0.15`.
The `StreamGet` and `StreamPost` combinators are defined in terms of the more general `Stream` The `StreamGet` and `StreamPost` combinators are defined in terms of the more general `Stream`
``` haskell ignore ``` haskell ignore
data Stream (method :: k1) (framing :: *) (contentType :: *) a data Stream (method :: k1) (status :: Nat) (framing :: *) (contentType :: *) (a :: *)
type StreamGet = Stream 'GET
type StreamPost = Stream 'POST type StreamGet = Stream 'GET 200
type StreamPost = Stream 'POST 200
``` ```
These describe endpoints that return a stream of values rather than just a single value. They not only take a single content type as a parameter, but also a framing strategy -- this specifies how the individual results are delineated from one another in the stream. The two standard strategies given with Servant are `NewlineFraming` and `NetstringFraming`, but others can be written to match other protocols. These describe endpoints that return a stream of values rather than just a
single value. They not only take a single content type as a parameter, but also
a framing strategy -- this specifies how the individual results are delineated
from one another in the stream. The three standard strategies given with
Servant are `NewlineFraming`, `NetstringFraming` and `NoFraming`, but others
can be written to match other protocols.
### `Capture` ### `Capture`
@ -170,13 +177,12 @@ type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
-- except that we explicitly say that "userid" -- except that we explicitly say that "userid"
-- must be an integer -- must be an integer
:<|> "user" :> Capture "userid" Integer :> DeleteNoContent '[JSON] NoContent :<|> "user" :> Capture "userid" Integer :> DeleteNoContent
-- equivalent to 'DELETE /user/:userid' -- equivalent to 'DELETE /user/:userid'
``` ```
In the second case, `DeleteNoContent` specifies a 204 response code, In the second case, `DeleteNoContent` specifies a 204 response code
`JSON` specifies the content types on which the handler will match, and that the response will always be empty.
and `NoContent` says that the response will always be empty.
### `QueryParam`, `QueryParams`, `QueryFlag` ### `QueryParam`, `QueryParams`, `QueryFlag`
@ -383,3 +389,30 @@ One example for this is if you want to serve a directory of static files along
with the rest of your API. But you can plug in everything that is an with the rest of your API. But you can plug in everything that is an
`Application`, e.g. a whole web application written in any of the web `Application`, e.g. a whole web application written in any of the web
frameworks that support `wai`. frameworks that support `wai`.
Be mindful! The `servant-server`'s router works by pattern-matching the
different routes that are composed using `:<|>`. `Raw`, as an escape hatch,
matches any route that hasn't been matched by previous patterns. Therefore,
any subsequent route will be silently ignored.
``` haskell
type UserAPI14 = Raw
:<|> "users" :> Get '[JSON] [User]
-- In this situation, the /users endpoint
-- will not be reachable because the Raw
-- endpoint matches requests before
```
A simple way to avoid this pitfall is to either use `Raw` as the last
definition, or to always have it under a static path.
``` haskell
type UserAPI15 = "files" :> Raw
-- The raw endpoint is under the /files
-- static path, so it won't match /users.
:<|> "users" :> Get '[JSON] [User]
type UserAPI16 = "users" :> Get '[JSON] [User]
:<|> Raw
-- The Raw endpoint is matched last, so
-- it won't overlap another endpoint.
```

View file

@ -47,7 +47,6 @@ module Authentication where
import Data.Aeson (ToJSON) import Data.Aeson (ToJSON)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.Map (Map, fromList) import Data.Map (Map, fromList)
import Data.Monoid ((<>))
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Proxy (Proxy (Proxy)) import Data.Proxy (Proxy (Proxy))
import Data.Text (Text) import Data.Text (Text)
@ -108,7 +107,7 @@ API with "private." Additionally, the private parts of our API use the
realm for this authentication is `"foo-realm"`). realm for this authentication is `"foo-realm"`).
Unfortunately we're not done. When someone makes a request to our `"private"` Unfortunately we're not done. When someone makes a request to our `"private"`
API, we're going to need to provide to servant the logic for validifying API, we're going to need to provide to servant the logic for validating
usernames and passwords. This adds a certain conceptual wrinkle in servant's usernames and passwords. This adds a certain conceptual wrinkle in servant's
design that we'll briefly discuss. If you want the **TL;DR**: we supply a lookup design that we'll briefly discuss. If you want the **TL;DR**: we supply a lookup
function to servant's new `Context` primitive. function to servant's new `Context` primitive.
@ -133,7 +132,7 @@ combinator. Using `Context`, we can supply a function of type
handler. This will allow the handler to check authentication and return a `User` handler. This will allow the handler to check authentication and return a `User`
to downstream handlers if successful. to downstream handlers if successful.
In practice we wrap `BasicAuthData -> Handler` into a slightly In practice we wrap `BasicAuthData -> Handler User` into a slightly
different function to better capture the semantics of basic authentication: different function to better capture the semantics of basic authentication:
``` haskell ignore ``` haskell ignore
@ -260,7 +259,7 @@ this.
Let's implement a trivial authentication scheme. We will protect our API by Let's implement a trivial authentication scheme. We will protect our API by
looking for a cookie named `"servant-auth-cookie"`. This cookie's value will looking for a cookie named `"servant-auth-cookie"`. This cookie's value will
contain a key from which we can lookup a `Account`. contain a key from which we can lookup an `Account`.
```haskell ```haskell
-- | An account type that we "fetch from the database" after -- | An account type that we "fetch from the database" after
@ -274,7 +273,7 @@ database = fromList [ ("key1", Account "Anne Briggs")
, ("key3", Account "Ghédalia Tazartès") , ("key3", Account "Ghédalia Tazartès")
] ]
-- | A method that, when given a password, will return a Account. -- | A method that, when given a password, will return an Account.
-- This is our bespoke (and bad) authentication logic. -- This is our bespoke (and bad) authentication logic.
lookupAccount :: ByteString -> Handler Account lookupAccount :: ByteString -> Handler Account
lookupAccount key = case Map.lookup key database of lookupAccount key = case Map.lookup key database of
@ -318,7 +317,7 @@ genAuthAPI = Proxy
Now we need to bring everything together for the server. We have the Now we need to bring everything together for the server. We have the
`AuthHandler Request Account` value and an `AuthProtected` endpoint. To bind these `AuthHandler Request Account` value and an `AuthProtected` endpoint. To bind these
together, we need to provide a [Type Family](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/type-families.html) together, we need to provide a [Type Family](https://downloads.haskell.org/~ghc/8.8.1/docs/html/users_guide/glasgow_exts.html#type-families)
instance that tells the `HasServer` instance that our `Context` will supply a instance that tells the `HasServer` instance that our `Context` will supply a
`Account` (via `AuthHandler Request Account`) and that downstream combinators will `Account` (via `AuthHandler Request Account`) and that downstream combinators will
have access to this `Account` value (or an error will be thrown if authentication have access to this `Account` value (or an error will be thrown if authentication
@ -346,7 +345,7 @@ genAuthServerContext = authHandler :. EmptyContext
-- | Our API, where we provide all the author-supplied handlers for each end -- | Our API, where we provide all the author-supplied handlers for each end
-- point. Note that 'privateDataFunc' is a function that takes 'Account' as an -- point. Note that 'privateDataFunc' is a function that takes 'Account' as an
-- argument. We dont' worry about the authentication instrumentation here, -- argument. We don't worry about the authentication instrumentation here,
-- that is taken care of by supplying context -- that is taken care of by supplying context
genAuthServer :: Server AuthGenAPI genAuthServer :: Server AuthGenAPI
genAuthServer = genAuthServer =
@ -368,10 +367,10 @@ genAuthMain = run 8080 (serveWithContext genAuthAPI genAuthServerContext genAuth
$ curl -XGET localhost:8080/private $ curl -XGET localhost:8080/private
Missing auth header Missing auth header
$ curl -XGET localhost:8080/private -H "servant-auth-cookie: key3" $ curl -XGET localhost:8080/private -H "Cookie: servant-auth-cookie=key3"
[{"ssshhh":"this is a secret: Ghédalia Tazartès"}] [{"ssshhh":"this is a secret: Ghédalia Tazartès"}]
$ curl -XGET localhost:8080/private -H "servant-auth-cookie: bad-key" $ curl -XGET localhost:8080/private -H "Cookie: servant-auth-cookie=bad-key"
Invalid Cookie Invalid Cookie
$ curl -XGET localhost:8080/public $ curl -XGET localhost:8080/public
@ -385,11 +384,11 @@ Creating a generalized, ad-hoc authentication scheme was fairly straight
forward: forward:
1. use the `AuthProtect` combinator to protect your API. 1. use the `AuthProtect` combinator to protect your API.
2. choose a application-specific data type used by your server when 2. choose an application-specific data type used by your server when
authentication is successful (in our case this was `Account`). authentication is successful (in our case this was `Account`).
3. Create a value of `AuthHandler Request Account` which encapsulates the 3. Create a value of `AuthHandler Request Account` which encapsulates the
authentication logic (`Request -> Handler Account`). This function authentication logic (`Request -> Handler Account`). This function
will be executed everytime a request matches a protected route. will be executed every time a request matches a protected route.
4. Provide an instance of the `AuthServerData` type family, specifying your 4. Provide an instance of the `AuthServerData` type family, specifying your
application-specific data type returned when authentication is successful (in application-specific data type returned when authentication is successful (in
our case this was `Account`). our case this was `Account`).

View file

@ -1,9 +1,9 @@
# Querying an API # Querying an API
While defining handlers that [serve an API](Server.lhs) has a lot to it, querying an API is simpler: we do not care about what happens inside the webserver, we just need to know how to talk to it and get a response back. That said, we usually have to write the querying functions by hand because the structure of the API isn't a first class citizen and can't be inspected to generate the client-side functions. While defining handlers that [serve an API](Server.html) has a lot to it, querying an API is simpler: we do not care about what happens inside the webserver, we just need to know how to talk to it and get a response back. That said, we usually have to write the querying functions by hand because the structure of the API isn't a first class citizen and can't be inspected to generate the client-side functions.
**servant** however has a way to inspect APIs, because APIs are just Haskell types and (GHC) Haskell lets us do quite a few things with types. In the same way that we look at an API type to deduce the types the handlers should have, we can inspect the structure of the API to *derive* Haskell functions that take one argument for each occurrence of `Capture`, `ReqBody`, `QueryParam` **servant** however has a way to inspect APIs, because APIs are just Haskell types and (GHC) Haskell lets us do quite a few things with types. In the same way that we look at an API type to deduce the types the handlers should have, we can inspect the structure of the API to *derive* Haskell functions that take one argument for each occurrence of `Capture`, `ReqBody`, `QueryParam`
and friends (see [the tutorial introduction](ApiType.lhs) for an overview). By *derive*, we mean that there's no code generation involved - the functions are defined just by the structure of the API type. and friends (see [the tutorial introduction](ApiType.html) for an overview). By *derive*, we mean that there's no code generation involved - the functions are defined just by the structure of the API type.
The source for this tutorial section is a literate Haskell file, so first we need to have some language extensions and imports: The source for this tutorial section is a literate Haskell file, so first we need to have some language extensions and imports:
@ -20,6 +20,9 @@ import GHC.Generics
import Network.HTTP.Client (newManager, defaultManagerSettings) import Network.HTTP.Client (newManager, defaultManagerSettings)
import Servant.API import Servant.API
import Servant.Client import Servant.Client
import Servant.Types.SourceT (foreach)
import qualified Servant.Client.Streaming as S
``` ```
Also, we need examples for some domain specific data types: Also, we need examples for some domain specific data types:
@ -155,46 +158,100 @@ Email {from = "great@company.com", to = "alp@foo.com", subject = "Hey Alp, we mi
The types of the arguments for the functions are the same as for (server-side) request handlers. The types of the arguments for the functions are the same as for (server-side) request handlers.
## Changing the monad the client functions live in
Just like `hoistServer` allows us to change the monad in which request handlers
of a web application live, we also have `hoistClient` for changing the monad
in which _client functions_ live. Consider the following trivial API:
``` haskell
type HoistClientAPI = Get '[JSON] Int :<|> Capture "n" Int :> Post '[JSON] Int
hoistClientAPI :: Proxy HoistClientAPI
hoistClientAPI = Proxy
```
We already know how to derive client functions for this API, and as we have
seen above they all return results in the `ClientM` monad when using `servant-client`.
However, `ClientM` is rarely (or never) the actual monad we need to use the client
functions in. Sometimes we need to run them in IO, sometimes in a custom monad
stack. `hoistClient` is a very simple solution to the problem of "changing" the monad
the clients run in.
``` haskell ignore
hoistClient
:: HasClient ClientM api -- we need a valid API
=> Proxy api -- a Proxy to the API type
-> (forall a. m a -> n a) -- a "monad conversion function" (natural transformation)
-> Client m api -- clients in the source monad
-> Client n api -- result: clients in the target monad
```
The "conversion function" argument above, just like the ones given to `hoistServer`, must
be able to turn an `m a` into an `n a` for any choice of type `a`.
Let's see this in action on our example. We first derive our client functions as usual,
with all of them returning a result in `ClientM`.
``` haskell
getIntClientM :: ClientM Int
postIntClientM :: Int -> ClientM Int
getIntClientM :<|> postIntClientM = client hoistClientAPI
```
And we finally decide that we want the handlers to run in IO instead, by
"post-applying" `runClientM` to a fixed client environment.
``` haskell
-- our conversion function has type: forall a. ClientM a -> IO a
-- the result has type:
-- Client IO HoistClientAPI = IO Int :<|> (Int -> IO Int)
getClients :: ClientEnv -> Client IO HoistClientAPI
getClients clientEnv
= hoistClient hoistClientAPI
( fmap (either (error . show) id)
. flip runClientM clientEnv
)
(client hoistClientAPI)
```
## Querying Streaming APIs. ## Querying Streaming APIs.
Consider the following streaming API type: Consider the following streaming API type:
``` haskell ``` haskell
type StreamAPI = "positionStream" :> StreamGet NewlineFraming JSON (ResultStream Position) type StreamAPI = "positionStream" :> StreamGet NewlineFraming JSON (SourceIO Position)
``` ```
Note that when we declared an API to serve, we specified a `StreamGenerator` as a producer of streams. Now we specify our result type as a `ResultStream`. With types that can be used both ways, if appropriate adaptors are written (in the form of `ToStreamGenerator` and `BuildFromStream` instances), then this asymmetry isn't necessary. Otherwise, if you want to share the same API across clients and servers, you can parameterize it like so: Note that we use the same `SourceIO` type as on the server-side
(this is different from `servant-0.14`).
``` haskell ignore However, we have to use different client, `Servant.Client.Streaming`,
type StreamAPI f = "positionStream" :> StreamGet NewlineFraming JSON (f Position) which can stream (but has different API).
type ServerStreamAPI = StreamAPI StreamGenerator
type ClientStreamAPI = StreamAPI ResultStream
```
In any case, here's how we write a function to query our API: In any case, here's how we write a function to query our API:
``` haskell ```haskell
streamAPI :: Proxy StreamAPI streamAPI :: Proxy StreamAPI
streamAPI = Proxy streamAPI = Proxy
posStream :: ClientM (ResultStream Position) posStream :: S.ClientM (SourceIO Position)
posStream = S.client streamAPI
posStream = client streamAPI
``` ```
And here's how to just print out all elements from a `ResultStream`, to give some idea of how to work with them. We'll get back a `SourceIO Position`. Instead of `runClientM`, the streaming
client provides `withClientM`: the underlying HTTP connection is open only
until the inner functions exits. Inside the block we can e.g just print out
all elements from a `SourceIO`, to give some idea of how to work with them.
``` haskell ``` haskell
printResultStream :: Show a => ResultStream a -> IO () printSourceIO :: Show a => ClientEnv -> S.ClientM (SourceIO a) -> IO ()
printResultStream (ResultStream k) = k $ \getResult -> printSourceIO env c = S.withClientM c env $ \e -> case e of
let loop = do Left err -> putStrLn $ "Error: " ++ show err
r <- getResult Right rs -> foreach fail print rs
case r of
Nothing -> return ()
Just x -> print x >> loop
in loop
``` ```
The stream is parsed and provided incrementally. So the above loop prints out each result as soon as it is received on the stream, rather than waiting until they are all available to print them at once. The stream is parsed and provided incrementally. So the above loop prints out
each result as soon as it is received on the stream, rather than waiting until
they are all available to print them at once.
You now know how to use **servant-client**! You now know how to use **servant-client**!

View file

@ -77,7 +77,7 @@ instance ToSample HelloMessage where
[ ("When a value is provided for 'name'", HelloMessage "Hello, Alp") [ ("When a value is provided for 'name'", HelloMessage "Hello, Alp")
, ("When 'name' is not specified", HelloMessage "Hello, anonymous coward") , ("When 'name' is not specified", HelloMessage "Hello, anonymous coward")
] ]
-- mutliple examples to display this time -- multiple examples to display this time
ci :: ClientInfo ci :: ClientInfo
ci = ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"] ci = ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
@ -108,7 +108,7 @@ apiDocs = docs exampleAPI
markdown :: API -> String markdown :: API -> String
``` ```
That lets us see what our API docs look down in markdown, by looking at `markdown apiDocs`. That lets us see what our API docs look like in markdown, by looking at `markdown apiDocs`.
````````` text ````````` text
## GET /hello ## GET /hello

View file

@ -228,13 +228,13 @@ data CommonGeneratorOptions = CommonGeneratorOptions
{ {
-- | function generating function names -- | function generating function names
functionNameBuilder :: FunctionName -> Text functionNameBuilder :: FunctionName -> Text
-- | name used when a user want to send the request body (to let you redefine it) -- | name used when a user wants to send the request body (to let you redefine it)
, requestBody :: Text , requestBody :: Text
-- | name of the callback parameter when the request was successful -- | name of the callback parameter when the request was successful
, successCallback :: Text , successCallback :: Text
-- | name of the callback parameter when the request reported an error -- | name of the callback parameter when the request reported an error
, errorCallback :: Text , errorCallback :: Text
-- | namespace on which we define the js function (empty mean local var) -- | namespace on which we define the js function (empty means local var)
, moduleName :: Text , moduleName :: Text
-- | a prefix that should be prepended to the URL in the generated JS -- | a prefix that should be prepended to the URL in the generated JS
, urlPrefix :: Text , urlPrefix :: Text
@ -477,7 +477,7 @@ data AngularOptions = AngularOptions
} }
``` ```
# Custom function name builder ## Custom function name builder
Servant comes with three name builders included: Servant comes with three name builders included:
@ -518,4 +518,3 @@ var get_books = function(q, onSuccess, onError)
} }
``` ```

View file

@ -29,7 +29,7 @@ import Prelude.Compat
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.Reader import Control.Monad.Reader
import Data.Aeson.Compat import Data.Aeson
import Data.Aeson.Types import Data.Aeson.Types
import Data.Attoparsec.ByteString import Data.Attoparsec.ByteString
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
@ -46,6 +46,7 @@ import Servant
import System.Directory import System.Directory
import Text.Blaze import Text.Blaze
import Text.Blaze.Html.Renderer.Utf8 import Text.Blaze.Html.Renderer.Utf8
import Servant.Types.SourceT (source)
import qualified Data.Aeson.Parser import qualified Data.Aeson.Parser
import qualified Text.Blaze.Html import qualified Text.Blaze.Html
``` ```
@ -95,12 +96,6 @@ users1 =
] ]
``` ```
Let's also write our API type.
``` haskell ignore
type UserAPI1 = "users" :> Get '[JSON] [User]
```
We can now take care of writing the actual webservice that will handle requests We can now take care of writing the actual webservice that will handle requests
to such an API. This one will be very simple, being reduced to just a single to such an API. This one will be very simple, being reduced to just a single
endpoint. The type of the web application is determined by the API type, endpoint. The type of the web application is determined by the API type,
@ -188,7 +183,7 @@ users2 = [isaac, albert]
Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
are going to separate the handlers with `:<|>` too! They must be provided in are going to separate the handlers with `:<|>` too! They must be provided in
the same order as in in the API type. the same order as in the API type.
``` haskell ``` haskell
server2 :: Server UserAPI2 server2 :: Server UserAPI2
@ -318,8 +313,8 @@ For reference, here's a list of some combinators from **servant**:
## The `FromHttpApiData`/`ToHttpApiData` classes ## The `FromHttpApiData`/`ToHttpApiData` classes
Wait... How does **servant** know how to decode the `Int`s from the URL? Or how Wait... How does **servant** know how to decode the `Int`s from the URL? Or how
to decode a `ClientInfo` value from the request body? This is what this and the to decode a `ClientInfo` value from the request body? The following three sections will
following two sections address. help us answer these questions.
`Capture`s and `QueryParam`s are represented by some textual value in URLs. `Capture`s and `QueryParam`s are represented by some textual value in URLs.
`Header`s are similarly represented by a pair of a header name and a `Header`s are similarly represented by a pair of a header name and a
@ -604,7 +599,7 @@ $ curl -H 'Accept: text/html' http://localhost:8081/persons
## The `Handler` monad ## The `Handler` monad
At the heart of the handlers is the monad they run in, namely a newtype `Handler` around `ExceptT ServantErr IO` At the heart of the handlers is the monad they run in, namely a newtype `Handler` around `ExceptT ServerError IO`
([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)). ([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
One might wonder: why this monad? The answer is that it is the One might wonder: why this monad? The answer is that it is the
simplest monad with the following properties: simplest monad with the following properties:
@ -622,12 +617,12 @@ newtype ExceptT e m a = ExceptT (m (Either e a))
``` ```
In short, this means that a handler of type `Handler a` is simply In short, this means that a handler of type `Handler a` is simply
equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO equivalent to a computation of type `IO (Either ServerError a)`, that is, an IO
action that either returns an error or a result. action that either returns an error or a result.
The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT) The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl/docs/Control-Monad-Except.html#t:ExceptT)
from which `ExceptT` comes is worth looking at. from which `ExceptT` comes is worth looking at.
Perhaps most importantly, `ExceptT` and `Handler` are an instances of `MonadError`, so Perhaps most importantly, `ExceptT` and `Handler` are instances of `MonadError`, so
`throwError` can be used to return an error from your handler (whereas `return` `throwError` can be used to return an error from your handler (whereas `return`
is enough to return a success). is enough to return a success).
@ -637,9 +632,9 @@ kind and abort early. The next two sections cover how to do just that.
### Performing IO ### Performing IO
Another important instances from the list above are `MonadIO m => MonadIO Other important instances from the list above are `MonadIO m => MonadIO
(ExceptT e m)`, and therefore also `MonadIO Handler` as there is `MonadIO IO` instance.. (ExceptT e m)`, and therefore also `MonadIO Handler` as there is a `MonadIO IO` instance.
[`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html) [`MonadIO`](http://hackage.haskell.org/package/base/docs/Control-Monad-IO-Class.html#t:MonadIO)
is a class from the **transformers** package defined as: is a class from the **transformers** package defined as:
``` haskell ignore ``` haskell ignore
@ -665,16 +660,16 @@ server5 = do
return (FileContent filecontent) return (FileContent filecontent)
``` ```
### Failing, through `ServantErr` ### Failing, through `ServerError`
If you want to explicitly fail at providing the result promised by an endpoint If you want to explicitly fail at providing the result promised by an endpoint
using the appropriate HTTP status code (not found, unauthorized, etc) and some using the appropriate HTTP status code (not found, unauthorized, etc) and some
error message, all you have to do is use the `throwError` function mentioned above error message, all you have to do is use the `throwError` function mentioned above
and provide it with the appropriate value of type `ServantErr`, which is and provide it with the appropriate value of type `ServerError`, which is
defined as: defined as:
``` haskell ignore ``` haskell ignore
data ServantErr = ServantErr data ServerError = ServerError
{ errHTTPCode :: Int { errHTTPCode :: Int
, errReasonPhrase :: String , errReasonPhrase :: String
, errBody :: ByteString -- lazy bytestring , errBody :: ByteString -- lazy bytestring
@ -690,7 +685,7 @@ use record update syntax:
failingHandler :: Handler () failingHandler :: Handler ()
failingHandler = throwError myerr failingHandler = throwError myerr
where myerr :: ServantErr where myerr :: ServerError
myerr = err503 { errBody = "Sorry dear user." } myerr = err503 { errBody = "Sorry dear user." }
``` ```
@ -721,7 +716,7 @@ $ curl --verbose http://localhost:8081/myfile.txt
> >
< HTTP/1.1 404 Not Found < HTTP/1.1 404 Not Found
[snip] [snip]
myfile.txt just isnt there, please leave this server alone. myfile.txt just isn't there, please leave this server alone.
$ echo Hello > myfile.txt $ echo Hello > myfile.txt
@ -823,7 +818,7 @@ If it doesn't exist, the handler will fail with a `404` status code.
`serveDirectoryWebApp` uses some standard settings that fit the use case of `serveDirectoryWebApp` uses some standard settings that fit the use case of
serving static files for most web apps. You can find out about the other serving static files for most web apps. You can find out about the other
options in the documentation of the `Servant.Utils.StaticFiles` module. options in the documentation of the `Servant.Server.StaticFiles` module.
## Nested APIs ## Nested APIs
@ -835,7 +830,7 @@ type UserAPI3 = -- view the user with given userid, in JSON
Capture "userid" Int :> Get '[JSON] User Capture "userid" Int :> Get '[JSON] User
:<|> -- delete the user with given userid. empty response :<|> -- delete the user with given userid. empty response
Capture "userid" Int :> DeleteNoContent '[JSON] NoContent Capture "userid" Int :> DeleteNoContent
``` ```
We can instead factor out the `userid`: We can instead factor out the `userid`:
@ -843,7 +838,7 @@ We can instead factor out the `userid`:
``` haskell ``` haskell
type UserAPI4 = Capture "userid" Int :> type UserAPI4 = Capture "userid" Int :>
( Get '[JSON] User ( Get '[JSON] User
:<|> DeleteNoContent '[JSON] NoContent :<|> DeleteNoContent
) )
``` ```
@ -901,13 +896,13 @@ type API1 = "users" :>
-- we factor out the Request Body -- we factor out the Request Body
type API2 = ReqBody '[JSON] User :> type API2 = ReqBody '[JSON] User :>
( Get '[JSON] User -- just display the same user back, don't register it ( Get '[JSON] User -- just display the same user back, don't register it
:<|> PostNoContent '[JSON] NoContent -- register the user. empty response :<|> PostNoContent -- register the user. empty response
) )
-- we factor out a Header -- we factor out a Header
type API3 = Header "Authorization" Token :> type API3 = Header "Authorization" Token :>
( Get '[JSON] SecretData -- get some secret data, if authorized ( Get '[JSON] SecretData -- get some secret data, if authorized
:<|> ReqBody '[JSON] SecretData :> PostNoContent '[JSON] NoContent -- add some secret data, if authorized :<|> ReqBody '[JSON] SecretData :> PostNoContent -- add some secret data, if authorized
) )
newtype Token = Token ByteString newtype Token = Token ByteString
@ -920,11 +915,11 @@ API type only at the end.
``` haskell ``` haskell
type UsersAPI = type UsersAPI =
Get '[JSON] [User] -- list users Get '[JSON] [User] -- list users
:<|> ReqBody '[JSON] User :> PostNoContent '[JSON] NoContent -- add a user :<|> ReqBody '[JSON] User :> PostNoContent -- add a user
:<|> Capture "userid" Int :> :<|> Capture "userid" Int :>
( Get '[JSON] User -- view a user ( Get '[JSON] User -- view a user
:<|> ReqBody '[JSON] User :> PutNoContent '[JSON] NoContent -- update a user :<|> ReqBody '[JSON] User :> PutNoContent -- update a user
:<|> DeleteNoContent '[JSON] NoContent -- delete a user :<|> DeleteNoContent -- delete a user
) )
usersServer :: Server UsersAPI usersServer :: Server UsersAPI
@ -953,11 +948,11 @@ usersServer = getUsers :<|> newUser :<|> userOperations
``` haskell ``` haskell
type ProductsAPI = type ProductsAPI =
Get '[JSON] [Product] -- list products Get '[JSON] [Product] -- list products
:<|> ReqBody '[JSON] Product :> PostNoContent '[JSON] NoContent -- add a product :<|> ReqBody '[JSON] Product :> PostNoContent -- add a product
:<|> Capture "productid" Int :> :<|> Capture "productid" Int :>
( Get '[JSON] Product -- view a product ( Get '[JSON] Product -- view a product
:<|> ReqBody '[JSON] Product :> PutNoContent '[JSON] NoContent -- update a product :<|> ReqBody '[JSON] Product :> PutNoContent -- update a product
:<|> DeleteNoContent '[JSON] NoContent -- delete a product :<|> DeleteNoContent -- delete a product
) )
data Product = Product { productId :: Int } data Product = Product { productId :: Int }
@ -1001,11 +996,11 @@ abstract that away:
-- indexed by values of type 'i' -- indexed by values of type 'i'
type APIFor a i = type APIFor a i =
Get '[JSON] [a] -- list 'a's Get '[JSON] [a] -- list 'a's
:<|> ReqBody '[JSON] a :> PostNoContent '[JSON] NoContent -- add an 'a' :<|> ReqBody '[JSON] a :> PostNoContent -- add an 'a'
:<|> Capture "id" i :> :<|> Capture "id" i :>
( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i' ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
:<|> ReqBody '[JSON] a :> PutNoContent '[JSON] NoContent -- update an 'a' :<|> ReqBody '[JSON] a :> PutNoContent -- update an 'a'
:<|> DeleteNoContent '[JSON] NoContent -- delete an 'a' :<|> DeleteNoContent -- delete an 'a'
) )
-- Build the appropriate 'Server' -- Build the appropriate 'Server'
@ -1133,14 +1128,14 @@ This is the webservice in action:
``` bash ``` bash
$ curl http://localhost:8081/a $ curl http://localhost:8081/a
1797 1797
$ curl http://localhost:8081/b $ curl http://localhost:8081/b -X GET -d '42.0' -H 'Content-Type: application/json'
"hi" true
``` ```
### An arrow is a reader too. ### An arrow is a reader too.
In previous versions of `servant` we had an `enter` to do what `hoistServer` In previous versions of `servant` we had an `enter` to do what `hoistServer`
does now. `enter` had a ambitious design goals, but was problematic in practice. does now. `enter` had an ambitious design goals, but was problematic in practice.
One problematic situation was when the source monad was `(->) r`, yet it's One problematic situation was when the source monad was `(->) r`, yet it's
handy in practice, because `(->) r` is isomorphic to `Reader r`. handy in practice, because `(->) r` is isomorphic to `Reader r`.
@ -1166,24 +1161,37 @@ app5 = serve readerAPI (hoistServer readerAPI funToHandler funServerT)
## Streaming endpoints ## Streaming endpoints
We can create endpoints that don't just give back a single result, but give back a *stream* of results, served one at a time. Stream endpoints only provide a single content type, and also specify what framing strategy is used to delineate the results. To serve these results, we need to give back a stream producer. Adapters can be written to `Pipes`, `Conduit` and the like, or written directly as `StreamGenerator`s. StreamGenerators are IO-based continuations that are handed two functions -- the first to write the first result back, and the second to write all subsequent results back. (This is to allow handling of situations where the entire stream is prefixed by a header, or where a boundary is written between elements, but not prior to the first element). The API of a streaming endpoint needs to explicitly specify which sort of generator it produces. Note that the generator itself is returned by a `Handler` action, so that additional IO may be done in the creation of one. We can create endpoints that don't just give back a single result, but give
back a *stream* of results, served one at a time. Stream endpoints only provide
a single content type, and also specify what framing strategy is used to
delineate the results. To serve these results, we need to give back a stream
producer. Adapters can be written to *Pipes*, *Conduit* and the like, or
written directly as `SourceIO`s. SourceIO builds upon servant's own `SourceT`
stream type (it's simpler than *Pipes* or *Conduit*).
The API of a streaming endpoint needs to explicitly specify which sort of
generator it produces. Note that the generator itself is returned by a
`Handler` action, so that additional IO may be done in the creation of one.
``` haskell ``` haskell
type StreamAPI = "userStream" :> StreamGet NewlineFraming JSON (StreamGenerator User) type StreamAPI = "userStream" :> StreamGet NewlineFraming JSON (SourceIO User)
streamAPI :: Proxy StreamAPI streamAPI :: Proxy StreamAPI
streamAPI = Proxy streamAPI = Proxy
streamUsers :: StreamGenerator User streamUsers :: SourceIO User
streamUsers = StreamGenerator $ \sendFirst sendRest -> do streamUsers = source [isaac, albert, albert]
sendFirst isaac
sendRest albert
sendRest albert
app6 :: Application app6 :: Application
app6 = serve streamAPI (return streamUsers) app6 = serve streamAPI (return streamUsers)
``` ```
This simple application returns a stream of `User` values encoded in JSON format, with each value separated by a newline. In this case, the stream will consist of the value of `isaac`, followed by the value of `albert`, then the value of `albert` a third time. Importantly, the stream is written back as results are produced, rather than all at once. This means first that results are delivered when they are available, and second, that if an exception interrupts production of the full stream, nonetheless partial results have already been written back. This simple application returns a stream of `User` values encoded in JSON
format, with each value separated by a newline. In this case, the stream will
consist of the value of `isaac`, followed by the value of `albert`, then the
value of `albert` a second time. Importantly, the stream is written back as
results are produced, rather than all at once. This means first that results
are delivered when they are available, and second, that if an exception
interrupts production of the full stream, nonetheless partial results have
already been written back.
## Conclusion ## Conclusion

View file

@ -3,13 +3,13 @@ Tutorial
This is an introductory tutorial to **servant**. Whilst browsing is fine, it makes more sense if you read the sections in order, or at least read the first section before anything else. This is an introductory tutorial to **servant**. Whilst browsing is fine, it makes more sense if you read the sections in order, or at least read the first section before anything else.
(Any comments, issues or feedback about the tutorial can be submitted Any comments, issues or feedback about the tutorial can be submitted
to `servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.) to `servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
install.rst
ApiType.lhs ApiType.lhs
Server.lhs Server.lhs
Client.lhs Client.lhs

68
doc/tutorial/install.rst Normal file
View file

@ -0,0 +1,68 @@
Install
========
cabal-install
--------
The whole tutorial is a `cabal <https://cabal.readthedocs.io/en/latest/>`_
project and can be built locally as follows:
.. code-block:: bash
$ git clone https://github.com/haskell-servant/servant.git
$ cd servant
# build
$ cabal new-build tutorial
# load in ghci to play with it
$ cabal new-repl tutorial
stack
--------
The servant `stack <https://docs.haskellstack.org/en/stable/README/>`_ template includes the working tutorial. To initialize this template, run:
.. code-block:: bash
$ stack new myproj servant
$ cd myproj
# build
$ stack build
# start server
$ stack exec myproj-exe
The code can be found in the `*.lhs` files under `doc/tutorial/` in the
repository. Feel free to edit it while you're reading this documentation and
see the effect of your changes.
nix
--------
`Nix <https://nixos.org/nix/>`_ users should feel free to take a look at
the `nix/shell.nix` file in the repository and use it to provision a suitable
environment to build and run the examples.
Note for Ubuntu users
--------
Ubuntu's packages for `ghc`, `cabal`, and `stack` are years out of date.
If the instructions above fail for you,
try replacing the Ubuntu packages with up-to-date versions.
First remove the installed versions:
.. code-block:: bash
# remove the obsolete versions
$ sudo apt remove ghc haskell-stack cabal-install
Then install fresh versions of the Haskell toolchain
using the `ghcup <https://www.haskell.org/ghcup/install/>`_ installer.
As of February 2022, one easy way to do this is by running a bootstrap script:
.. code-block:: bash
$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
The script is interactive and will prompt you for details about what
you want installed and where. To install manually,
see `the detailed instructions <https://www.haskell.org/ghcup/install/#manual-install>`_.

View file

@ -1 +1,11 @@
{-# OPTIONS_GHC -F -pgmF hspec-discover #-} module Main where
import qualified JavascriptSpec
import Test.Hspec (Spec, hspec, describe)
main :: IO ()
main = hspec spec
spec :: Spec
spec = describe "Javascript" JavascriptSpec.spec

View file

@ -1,13 +0,0 @@
dependencies:
- name: servant
path: ../../servant
- name: servant-server
path: ../../servant-server
- name: servant-client
path: ../../servant-client
- name: servant-js
path: ../../servant-js
- name: servant-docs
path: ../../servant-docs
- name: servant-foreign
path: ../../servant-foreign

View file

@ -1,22 +1,20 @@
cabal-version: 2.2
name: tutorial name: tutorial
version: 0.10 version: 0.10
synopsis: The servant tutorial synopsis: The servant tutorial
description: description:
The servant tutorial can be found at The servant tutorial can be found at
<http://haskell-servant.readthedocs.org/> <http://docs.servant.dev/>
homepage: http://haskell-servant.readthedocs.org/ homepage: http://docs.servant.dev/
category: Servant, Documentation category: Servant, Documentation
license: BSD3 license: BSD-3-Clause
license-file: LICENSE license-file: LICENSE
author: Servant Contributors author: Servant Contributors
maintainer: haskell-servant-maintainers@googlegroups.com maintainer: haskell-servant-maintainers@googlegroups.com
build-type: Simple build-type: Simple
cabal-version: >=1.10
tested-with: tested-with:
GHC==7.8.4 GHC==8.6.5
GHC==7.10.3 GHC==8.8.3, GHC ==8.10.7
GHC==8.0.2
GHC==8.2.2
extra-source-files: extra-source-files:
static/index.html static/index.html
static/ui.js static/ui.js
@ -34,9 +32,8 @@ library
-- Packages `servant` depends on. -- Packages `servant` depends on.
-- We don't need to specify bounds here as this package is never released. -- We don't need to specify bounds here as this package is never released.
build-depends: build-depends:
base >= 4.7 && <4.11 base >= 4.7 && <5
, aeson , aeson
, aeson-compat
, attoparsec , attoparsec
, base-compat , base-compat
, bytestring , bytestring
@ -66,16 +63,15 @@ library
blaze-html >= 0.9.0.1 && < 0.10 blaze-html >= 0.9.0.1 && < 0.10
, blaze-markup >= 0.8.0.0 && < 0.9 , blaze-markup >= 0.8.0.0 && < 0.9
, cookie >= 0.4.3 && < 0.5 , cookie >= 0.4.3 && < 0.5
, js-jquery >= 3.2.1 && < 3.3 , js-jquery >= 3.3.1 && < 3.4
, lucid >= 2.9.9 && < 2.10 , lucid >= 2.9.11 && < 2.12
, mtl-compat >= 0.2.1 && < 0.3 , random >= 1.1 && < 1.3
, random >= 1.1 && < 1.2
, servant-js >= 0.9 && < 0.10 , servant-js >= 0.9 && < 0.10
, time >= 1.4.2 && < 1.9 , time >= 1.6.0.1 && < 1.13
-- For legacy tools, we need to specify build-depends too -- For legacy tools, we need to specify build-depends too
build-depends: markdown-unlit >= 0.4.1 && <0.5 build-depends: markdown-unlit >= 0.5.0 && <0.6
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4.1 && <0.5 build-tool-depends: markdown-unlit:markdown-unlit >= 0.5.0 && <0.6
test-suite spec test-suite spec
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
@ -84,8 +80,6 @@ test-suite spec
hs-source-dirs: test hs-source-dirs: test
main-is: Spec.hs main-is: Spec.hs
other-modules: JavascriptSpec other-modules: JavascriptSpec
build-tool-depends:
hspec-discover:hspec-discover
build-depends: base build-depends: base
, tutorial , tutorial
, hspec , hspec

22
ghcjs.nix Normal file
View file

@ -0,0 +1,22 @@
let reflex-platform = import (builtins.fetchTarball
{ name = "reflex-platform";
url = "https://github.com/reflex-frp/reflex-platform/archive/1aba6f367982bd6dd78ec2fda75ab246a62d32c5.tar.gz";
}) {};
pkgs = import ./nix/nixpkgs.nix; in
pkgs.stdenv.mkDerivation {
name = "ghcjs-shell";
buildInputs =
[ (reflex-platform.ghcjs.ghcWithPackages (p: with p; [
attoparsec
hashable
]))
pkgs.cabal-install
pkgs.gmp
pkgs.haskellPackages.cabal-plan
pkgs.haskellPackages.hspec-discover
pkgs.nodejs
pkgs.perl
pkgs.zlib
];
}

View file

@ -21,3 +21,21 @@ a particular ghc version, e.g:
``` sh ``` sh
$ nix-shell nix/shell.nix --argstr compiler ghcHEAD $ nix-shell nix/shell.nix --argstr compiler ghcHEAD
``` ```
**Possible GHC versions**
- `ghc865Binary`
- `ghc884`
- `ghc8104` - default
- `ghc901`
### Cabal users
GHC version can be chosen via the nix-shell parameter
`cabal build all`
### Stack version
Since the ghc version is set by the LTS version, it is preferable to use the `ghc8104` version parameter for the nix-shell.
`stack --no-nix --system-ghc <command>`

4
nix/nixpkgs.json Normal file
View file

@ -0,0 +1,4 @@
{
"rev" : "05f0934825c2a0750d4888c4735f9420c906b388",
"sha256" : "1g8c2w0661qn89ajp44znmwfmghbbiygvdzq0rzlvlpdiz28v6gy"
}

4
nix/nixpkgs.nix Normal file
View file

@ -0,0 +1,4 @@
import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/21.05.tar.gz";
sha256 = "sha256:1ckzhh24mgz6jd1xhfgx0i9mijk6xjqxwsshnvq789xsavrmsc36";
}) {}

View file

@ -1,21 +1,20 @@
{ pkgs ? import <nixpkgs> {} { compiler ? "ghc8104"
, compiler ? "ghc821"
, tutorial ? false , tutorial ? false
, pkgs ? import ./nixpkgs.nix
}: }:
with pkgs; with pkgs;
let let
ghc = haskell.packages.${compiler}.ghcWithPackages (_: []); ghc = haskell.packages.${compiler}.ghcWithPackages (_: []);
docstuffs = python3.withPackages (ps: with ps; [ recommonmark sphinx sphinx_rtd_theme ]); docstuffs = python3.withPackages (ps: with ps; [ recommonmark sphinx sphinx_rtd_theme ]);
in in
stdenv.mkDerivation {
stdenv.mkDerivation {
name = "servant-dev"; name = "servant-dev";
buildInputs = [ ghc zlib python3 wget ] buildInputs = [ ghc zlib python3 wget cabal-install postgresql openssl stack haskellPackages.hspec-discover ]
++ (if tutorial then [docstuffs postgresql] else []); ++ (if tutorial then [docstuffs postgresql] else []);
shellHook = '' shellHook = ''
eval $(grep export ${ghc}/bin/ghc) eval $(grep export ${ghc}/bin/ghc)
export LD_LIBRARY_PATH="${zlib}/lib"; export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"${zlib}/lib";
''; '';
} }

1
servant-auth/README.md Symbolic link
View file

@ -0,0 +1 @@
servant-auth-server/README.lhs

View file

@ -0,0 +1 @@
:set -isrc -itest -idoctest/ghci-wrapper/src

View file

@ -0,0 +1,26 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [PVP Versioning](https://pvp.haskell.org/).
## [Unreleased]
## [0.4.1.0] - 2020-10-06
- Support generic Bearer token auth
## [0.4.0.0] - 2019-03-08
## Changed
- #145 Support servant-0.16 in tests @domenkozar
- #145 Drop GHC 7.10 support @domenkozar
## [0.3.3.0] - 2018-06-18
### Added
- Support for GHC 8.4 by @phadej
- Support for servant-0.14 by @phadej
- Changelog by @domenkozar

View file

@ -0,0 +1,31 @@
Copyright Julian K. Arni (c) 2015
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Julian K. Arni nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View file

@ -0,0 +1,80 @@
cabal-version: 2.2
name: servant-auth-client
version: 0.4.1.0
synopsis: servant-client/servant-auth compatibility
description: This package provides instances that allow generating clients from
<https://hackage.haskell.org/package/servant servant>
APIs that use
<https://hackage.haskell.org/package/servant-auth servant-auth's> @Auth@ combinator.
.
For a quick overview of the usage, see the <https://github.com/haskell-servant/servant/tree/master/servant-auth#readme README>.
category: Web, Servant, Authentication
homepage: https://github.com/haskell-servant/servant/tree/master/servant-auth#readme
bug-reports: https://github.com/haskell-servant/servant/issues
author: Julian K. Arni
maintainer: jkarni@gmail.com
copyright: (c) Julian K. Arni
license: BSD-3-Clause
license-file: LICENSE
tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1
build-type: Simple
extra-source-files:
CHANGELOG.md
source-repository head
type: git
location: https://github.com/haskell-servant/servant
library
hs-source-dirs:
src
default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
ghc-options: -Wall
build-depends:
base >= 4.10 && < 4.18
, bytestring >= 0.10.6.0 && < 0.12
, containers >= 0.5.6.2 && < 0.7
, servant-auth == 0.4.*
, servant >= 0.13 && < 0.20
, servant-client-core >= 0.13 && < 0.20
exposed-modules:
Servant.Auth.Client
Servant.Auth.Client.Internal
default-language: Haskell2010
test-suite spec
type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs:
test
default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators
ghc-options: -Wall
build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.10
-- dependencies with bounds inherited from the library stanza
build-depends:
base
, servant-client
, servant-auth
, servant
, servant-auth-client
-- test dependencies
build-depends:
hspec >= 2.5.5 && < 2.10
, QuickCheck >= 2.11.3 && < 2.15
, aeson >= 1.3.1.1 && < 3
, bytestring >= 0.10.6.0 && < 0.12
, http-client >= 0.5.13.1 && < 0.8
, http-types >= 0.12.2 && < 0.13
, servant-auth-server >= 0.4.2.0 && < 0.5
, servant-server >= 0.13 && < 0.20
, time >= 1.5.0.1 && < 1.13
, transformers >= 0.4.2.0 && < 0.6
, wai >= 3.2.1.2 && < 3.3
, warp >= 3.2.25 && < 3.4
, jose >= 0.10 && < 0.11
other-modules:
Servant.Auth.ClientSpec
default-language: Haskell2010

View file

@ -0,0 +1,3 @@
module Servant.Auth.Client (Token(..), Bearer) where
import Servant.Auth.Client.Internal (Bearer, Token(..))

View file

@ -0,0 +1,64 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
#if __GLASGOW_HASKELL__ == 800
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
#endif
module Servant.Auth.Client.Internal where
import qualified Data.ByteString as BS
import Data.Monoid
import Data.Proxy (Proxy (..))
import Data.String (IsString)
import GHC.Exts (Constraint)
import GHC.Generics (Generic)
import Servant.API ((:>))
import Servant.Auth
import Servant.Client.Core
import Data.Sequence ((<|))
-- | A simple bearer token.
newtype Token = Token { getToken :: BS.ByteString }
deriving (Eq, Show, Read, Generic, IsString)
type family HasBearer xs :: Constraint where
HasBearer (Bearer ': xs) = ()
HasBearer (JWT ': xs) = ()
HasBearer (x ': xs) = HasBearer xs
HasBearer '[] = BearerAuthNotEnabled
class BearerAuthNotEnabled
-- | @'HasBearer' auths@ is nominally a redundant constraint, but ensures we're not
-- trying to send a token to an API that doesn't accept them.
instance (HasBearer auths, HasClient m api) => HasClient m (Auth auths a :> api) where
type Client m (Auth auths a :> api) = Token -> Client m api
clientWithRoute m _ req (Token token)
= clientWithRoute m (Proxy :: Proxy api)
$ req { requestHeaders = ("Authorization", headerVal) <| requestHeaders req }
where
headerVal = "Bearer " <> token
#if MIN_VERSION_servant_client_core(0,14,0)
hoistClientMonad pm _ nt cl = hoistClientMonad pm (Proxy :: Proxy api) nt . cl
#endif
-- * Authentication combinators
-- | A Bearer token in the Authorization header:
--
-- @Authorization: Bearer <token>@
--
-- This can be any token recognized by the server, for example,
-- a JSON Web Token (JWT).
--
-- Note that, since the exact way the token is validated is not specified,
-- this combinator can only be used in the client. The server would not know
-- how to validate it, while the client does not care.
-- If you want to implement Bearer authentication in your server, you have to
-- choose a specific combinator, such as 'JWT'.
data Bearer

Some files were not shown because too many files have changed in this diff Show more