git.iko.soy/typeable/servant
on 0be69cb help sign in
.travis.yml+30−11
since splitsnapshots
unifiedsplit
@@ -13,7 +13,7 @@ git:
13branches:
13branches:
14 only:
14 only:
15 - master
15 - master
16− - release-0.12
16+ - release-0.14
17
17
18cache:
18cache:
19 directories:
19 directories:
@@ -65,17 +65,21 @@ install:
65 - BENCH=${BENCH---enable-benchmarks}
65 - BENCH=${BENCH---enable-benchmarks}
66 - TEST=${TEST---enable-tests}
66 - TEST=${TEST---enable-tests}
67 - HADDOCK=${HADDOCK-true}
67 - HADDOCK=${HADDOCK-true}
68− - INSTALLED=${INSTALLED-true}
68+ - UNCONSTRAINED=${UNCONSTRAINED-true}
69+ - NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}
69 - GHCHEAD=${GHCHEAD-false}
70 - GHCHEAD=${GHCHEAD-false}
70 - travis_retry cabal update -v
71 - travis_retry cabal update -v
71 - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
72 - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
72 - rm -fv cabal.project cabal.project.local
73 - rm -fv cabal.project cabal.project.local
73 - "if [ $HCNUMVER -ge 70800 ]; then sed -i.bak 's/-- ghc-options:.*/ghc-options: -j2/' ${HOME}/.cabal/config; fi"
74 - "if [ $HCNUMVER -ge 70800 ]; then sed -i.bak 's/-- ghc-options:.*/ghc-options: -j2/' ${HOME}/.cabal/config; fi"
74 - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
75 - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
75− - "printf 'packages: \"servant\" \"servant-client\" \"servant-client-core\" \"servant-docs\" \"servant-foreign\" \"servant-server\" \"doc/tutorial\" \"doc/cookbook/basic-auth\" \"doc/cookbook/db-postgres-pool\" \"doc/cookbook/db-sqlite-simple\" \"doc/cookbook/https\" \"doc/cookbook/pagination\" \"doc/cookbook/structuring-apis\" \"doc/cookbook/using-custom-monad\"\\n' > cabal.project"
76+ - "printf 'packages: \"servant\" \"servant-client\" \"servant-client-core\" \"servant-docs\" \"servant-foreign\" \"servant-server\" \"doc/tutorial\" \"doc/cookbook/basic-auth\" \"doc/cookbook/db-postgres-pool\" \"doc/cookbook/db-sqlite-simple\" \"doc/cookbook/file-upload\" \"doc/cookbook/https\" \"doc/cookbook/jwt-and-basic-auth\" \"doc/cookbook/pagination\" \"doc/cookbook/structuring-apis\" \"doc/cookbook/using-custom-monad\"\\n' > cabal.project"
76 - "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
77 - "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
77− - "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, http-media:base' >> cabal.project"
78+ - "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, servant-pagination:servant,servant-pagination:servant-server' >> cabal.project"
79+ - touch cabal.project.local
80+ - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
81+ - cat cabal.project || true
78− - cat cabal.project
82+ - cat cabal.project.local || true
79 - if [ -f "servant/configure.ac" ]; then
83 - if [ -f "servant/configure.ac" ]; then
80 (cd "servant" && autoreconf -i);
84 (cd "servant" && autoreconf -i);
81 fi
85 fi
@@ -106,9 +110,15 @@ install:
106 - if [ -f "doc/cookbook/db-sqlite-simple/configure.ac" ]; then
110 - if [ -f "doc/cookbook/db-sqlite-simple/configure.ac" ]; then
107 (cd "doc/cookbook/db-sqlite-simple" && autoreconf -i);
111 (cd "doc/cookbook/db-sqlite-simple" && autoreconf -i);
108 fi
112 fi
113+ - if [ -f "doc/cookbook/file-upload/configure.ac" ]; then
114+ (cd "doc/cookbook/file-upload" && autoreconf -i);
115+ fi
109 - if [ -f "doc/cookbook/https/configure.ac" ]; then
116 - if [ -f "doc/cookbook/https/configure.ac" ]; then
110 (cd "doc/cookbook/https" && autoreconf -i);
117 (cd "doc/cookbook/https" && autoreconf -i);
111 fi
118 fi
119+ - if [ -f "doc/cookbook/jwt-and-basic-auth/configure.ac" ]; then
120+ (cd "doc/cookbook/jwt-and-basic-auth" && autoreconf -i);
121+ fi
112 - if [ -f "doc/cookbook/pagination/configure.ac" ]; then
122 - if [ -f "doc/cookbook/pagination/configure.ac" ]; then
113 (cd "doc/cookbook/pagination" && autoreconf -i);
123 (cd "doc/cookbook/pagination" && autoreconf -i);
114 fi
124 fi
@@ -119,7 +129,7 @@ install:
119 (cd "doc/cookbook/using-custom-monad" && autoreconf -i);
129 (cd "doc/cookbook/using-custom-monad" && autoreconf -i);
120 fi
130 fi
121 - rm -f cabal.project.freeze
131 - rm -f cabal.project.freeze
122− - 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/basic-auth"/dist "doc/cookbook/db-postgres-pool"/dist "doc/cookbook/db-sqlite-simple"/dist "doc/cookbook/https"/dist "doc/cookbook/pagination"/dist "doc/cookbook/structuring-apis"/dist "doc/cookbook/using-custom-monad"/dist
132+ - 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/basic-auth"/dist "doc/cookbook/db-postgres-pool"/dist "doc/cookbook/db-sqlite-simple"/dist "doc/cookbook/file-upload"/dist "doc/cookbook/https"/dist "doc/cookbook/jwt-and-basic-auth"/dist "doc/cookbook/pagination"/dist "doc/cookbook/structuring-apis"/dist "doc/cookbook/using-custom-monad"/dist
123 - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
133 - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
124
134
125# Here starts the actual work to be performed for the package under test;
135# Here starts the actual work to be performed for the package under test;
@@ -137,22 +147,26 @@ script:
137 - (cd "doc/cookbook/basic-auth" && cabal sdist)
147 - (cd "doc/cookbook/basic-auth" && cabal sdist)
138 - (cd "doc/cookbook/db-postgres-pool" && cabal sdist)
148 - (cd "doc/cookbook/db-postgres-pool" && cabal sdist)
139 - (cd "doc/cookbook/db-sqlite-simple" && cabal sdist)
149 - (cd "doc/cookbook/db-sqlite-simple" && cabal sdist)
150+ - (cd "doc/cookbook/file-upload" && cabal sdist)
140 - (cd "doc/cookbook/https" && cabal sdist)
151 - (cd "doc/cookbook/https" && cabal sdist)
152+ - (cd "doc/cookbook/jwt-and-basic-auth" && cabal sdist)
141 - (cd "doc/cookbook/pagination" && cabal sdist)
153 - (cd "doc/cookbook/pagination" && cabal sdist)
142 - (cd "doc/cookbook/structuring-apis" && cabal sdist)
154 - (cd "doc/cookbook/structuring-apis" && cabal sdist)
143 - (cd "doc/cookbook/using-custom-monad" && cabal sdist)
155 - (cd "doc/cookbook/using-custom-monad" && cabal sdist)
144 - echo -en 'travis_fold:end:sdist\\r'
156 - echo -en 'travis_fold:end:sdist\\r'
145 - echo Unpacking... && echo -en 'travis_fold:start:unpack\\r'
157 - echo Unpacking... && echo -en 'travis_fold:start:unpack\\r'
146− - 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/basic-auth"/dist/cookbook-basic-auth-*.tar.gz "doc/cookbook/db-postgres-pool"/dist/cookbook-db-postgres-pool-*.tar.gz "doc/cookbook/db-sqlite-simple"/dist/cookbook-db-sqlite-simple-*.tar.gz "doc/cookbook/https"/dist/cookbook-https-*.tar.gz "doc/cookbook/pagination"/dist/cookbook-pagination-*.tar.gz "doc/cookbook/structuring-apis"/dist/cookbook-structuring-apis-*.tar.gz "doc/cookbook/using-custom-monad"/dist/cookbook-using-custom-monad-*.tar.gz ${DISTDIR}/
158+ - 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/basic-auth"/dist/cookbook-basic-auth-*.tar.gz "doc/cookbook/db-postgres-pool"/dist/cookbook-db-postgres-pool-*.tar.gz "doc/cookbook/db-sqlite-simple"/dist/cookbook-db-sqlite-simple-*.tar.gz "doc/cookbook/file-upload"/dist/cookbook-file-upload-*.tar.gz "doc/cookbook/https"/dist/cookbook-https-*.tar.gz "doc/cookbook/jwt-and-basic-auth"/dist/cookbook-jwt-and-basic-auth-*.tar.gz "doc/cookbook/pagination"/dist/cookbook-pagination-*.tar.gz "doc/cookbook/structuring-apis"/dist/cookbook-structuring-apis-*.tar.gz "doc/cookbook/using-custom-monad"/dist/cookbook-using-custom-monad-*.tar.gz ${DISTDIR}/
147 - cd ${DISTDIR} || false
159 - cd ${DISTDIR} || false
148 - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
160 - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
149− - "printf 'packages: servant-*/*.cabal servant-client-*/*.cabal servant-client-core-*/*.cabal servant-docs-*/*.cabal servant-foreign-*/*.cabal servant-server-*/*.cabal tutorial-*/*.cabal cookbook-basic-auth-*/*.cabal cookbook-db-postgres-pool-*/*.cabal cookbook-db-sqlite-simple-*/*.cabal cookbook-https-*/*.cabal cookbook-pagination-*/*.cabal cookbook-structuring-apis-*/*.cabal cookbook-using-custom-monad-*/*.cabal\\n' > cabal.project"
161+ - "printf 'packages: servant-*/*.cabal servant-client-*/*.cabal servant-client-core-*/*.cabal servant-docs-*/*.cabal servant-foreign-*/*.cabal servant-server-*/*.cabal tutorial-*/*.cabal cookbook-basic-auth-*/*.cabal cookbook-db-postgres-pool-*/*.cabal cookbook-db-sqlite-simple-*/*.cabal cookbook-file-upload-*/*.cabal cookbook-https-*/*.cabal cookbook-jwt-and-basic-auth-*/*.cabal cookbook-pagination-*/*.cabal cookbook-structuring-apis-*/*.cabal cookbook-using-custom-monad-*/*.cabal\\n' > cabal.project"
150 - "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
162 - "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
151− - "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, http-media:base' >> cabal.project"
163+ - "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, servant-pagination:servant,servant-pagination:servant-server' >> cabal.project"
164+ - touch cabal.project.local
165+ - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
166+ - cat cabal.project || true
152− - cat cabal.project
167+ - cat cabal.project.local || true
153 - echo -en 'travis_fold:end:unpack\\r'
168 - echo -en 'travis_fold:end:unpack\\r'
154
169
155−
156 - echo Building with tests and benchmarks... && echo -en 'travis_fold:start:build-everything\\r'
170 - echo Building with tests and benchmarks... && echo -en 'travis_fold:start:build-everything\\r'
157 # build & run tests, build benchmarks
171 # build & run tests, build benchmarks
158 - cabal new-build -w ${HC} ${TEST} ${BENCH} all
172 - cabal new-build -w ${HC} ${TEST} ${BENCH} all
@@ -165,5 +179,10 @@ script:
165 - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
179 - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
166
180
167 - echo -en 'travis_fold:end:haddock\\r'
181 - echo -en 'travis_fold:end:haddock\\r'
182+ - echo Building without installed constraints for packages in global-db... && echo -en 'travis_fold:start:build-installed\\r'
183+ # Build without installed constraints for packages in global-db
184+ - if $UNCONSTRAINED; then rm -f cabal.project.local; echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks all; else echo "Not building without installed constraints"; fi
185+
186+ - echo -en 'travis_fold:end:build-installed\\r'
168# REGENDATA ["--config=cabal.make-travis-yml","--output=.travis.yml","cabal.project"]
187# REGENDATA ["--config=cabal.make-travis-yml","--output=.travis.yml","cabal.project"]
169# EOF
188# EOF
cabal.make-travis-yml+1−1
@@ -1,5 +1,5 @@
1folds: all-but-test
1folds: all-but-test
2−branches: master release-0.12
2+branches: master release-0.14
3
3
4-- We have inplace packages (servant-js) so we skip installing dependencies in a separate step
4-- We have inplace packages (servant-js) so we skip installing dependencies in a separate step
5install-dependencies-step: False
5install-dependencies-step: False
cabal.project+2−8
@@ -11,11 +11,9 @@ packages: servant/
11 doc/cookbook/basic-auth
11 doc/cookbook/basic-auth
12 doc/cookbook/db-postgres-pool
12 doc/cookbook/db-postgres-pool
13 doc/cookbook/db-sqlite-simple
13 doc/cookbook/db-sqlite-simple
14− -- MkLink changed
15− -- doc/cookbook/file-upload
14+ doc/cookbook/file-upload
16 doc/cookbook/https
15 doc/cookbook/https
17− -- servant-auth-* doesn't support GHC-8.4
18− -- doc/cookbook/jwt-and-basic-auth
16+ doc/cookbook/jwt-and-basic-auth
19 doc/cookbook/pagination
17 doc/cookbook/pagination
20 doc/cookbook/structuring-apis
18 doc/cookbook/structuring-apis
21 doc/cookbook/using-custom-monad
19 doc/cookbook/using-custom-monad
@@ -30,9 +28,5 @@ constraints:
30 memory <0.14.12 || >0.14.12
28 memory <0.14.12 || >0.14.12
31
29
32allow-newer:
30allow-newer:
33− http-media:base
34−
35−allow-newer:
36− servant-js:servant,
37 servant-pagination:servant,
31 servant-pagination:servant,
38 servant-pagination:servant-server
32 servant-pagination:servant-server
doc/index.rst+11−9
@@ -3,22 +3,24 @@ servant – A Type-Level Web DSL
3
3
4.. image:: https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png
4.. image:: https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png
5
5
6−**servant** is a set of packages for declaring web APIs at the type-level and
6+**servant** is a set of Haskell libraries for writing *type-safe* web
7−then using those API specifications to:
7+applications but also *deriving* clients (in Haskell and other languages) or
8+generating documentation for them, and more.
8
9
9−- write servers (this part of **servant** can be considered a web framework),
10+This is achieved by taking as input a description of the web API
10−- obtain client functions (in haskell),
11+as a Haskell type. Servant is then able to check that your server-side request
11−- generate client functions for other programming languages,
12+handlers indeed implement your web API faithfully, or to automatically derive
12−- generate documentation for your web applications
13+Haskell functions that can hit a web application that implements this API,
13−- and more...
14+generate a Swagger description or code for client functions in some other
15+languages directly.
14
16
15−All in a type-safe manner.
17+If you would like to learn more, click the tutorial link below.
16
18
17.. toctree::
19.. toctree::
18 :maxdepth: 2
20 :maxdepth: 2
19
21
20− introduction.rst
21 tutorial/index.rst
22 tutorial/index.rst
22 cookbook/index.rst
23 cookbook/index.rst
23 examples.md
24 examples.md
24 links.rst
25 links.rst
26+ principles.rst
doc/introduction.rst → doc/principles.rstrenamed+2−2
@@ -1,5 +1,5 @@
1−Introduction
1+Principles
2−------------
2+----------
3
3
4**servant** has the following guiding principles:
4**servant** has the following guiding principles:
5
5
doc/tutorial/Javascript.lhs+1−2
@@ -477,7 +477,7 @@ data AngularOptions = AngularOptions
477 }
477 }
478```
478```
479
479
480−# Custom function name builder
480+## Custom function name builder
481
481
482Servant comes with three name builders included:
482Servant comes with three name builders included:
483
483
@@ -518,4 +518,3 @@ var get_books = function(q, onSuccess, onError)
518}
518}
519
519
520```
520```
521−
doc/tutorial/index.rst+21−2
@@ -3,9 +3,28 @@ Tutorial
3
3
4This 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.
4This 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.
5
5
6−(Any comments, issues or feedback about the tutorial can be submitted
6+Any comments, issues or feedback about the tutorial can be submitted
7−to `servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.)
7+to `servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.
8
8
9+In fact, the whole tutorial is a `cabal <https://cabal.readthedocs.io/en/latest/>`_
10+project and can be built and played with locally as follows:
11+
12+.. code-block:: bash
13+
14+ $ git clone https://github.com/haskell-servant/servant.git
15+ $ cd servant
16+ # build
17+ $ cabal new-build tutorial
18+ # load in ghci to play with it
19+ $ cabal new-repl tutorial
20+
21+The code can be found in the `*.lhs` files under `doc/tutorial/` in the
22+repository. Feel free to edit it while you're reading this documentation and
23+see the effect of your changes.
24+
25+`Nix <https://nixos.org/nix/>`_ users should feel free to take a look at
26+the `nix/shell.nix` file in the repository and use it to provision a suitable
27+environment to build and run the examples.
9
28
10.. toctree::
29.. toctree::
11 :maxdepth: 1
30 :maxdepth: 1
servant-client-core/CHANGELOG.md+42−2
@@ -4,10 +4,50 @@
40.14
40.14
5----
5----
6
6
7−- Add a `hoistClientMonad` method to the `HasClient` typeclass, for
7+- `Stream` takes a status code argument
8+
9+ ```diff
10+ -Stream method framing ctype a
11+ +Stream method status framing ctype a
12+ ```
13+
14+ ([#966](https://github.com/haskell-servant/servant/pull/966)
15+ [#972](https://github.com/haskell-servant/servant/pull/972))
16+
17+- `ToStreamGenerator` definition changed, so it's possible to write an instance
18+ for conduits.
19+
20+ ```diff
21+ -class ToStreamGenerator f a where
22+ - toStreamGenerator :: f a -> StreamGenerator a
23+ +class ToStreamGenerator a b | a -> b where
24+ + toStreamGenerator :: a -> StreamGenerator b
25+ ```
26+
27+ ([#959](https://github.com/haskell-servant/servant/pull/959))
28+
29+- Added `NoFraming` streaming strategy
30+ ([#959](https://github.com/haskell-servant/servant/pull/959))
31+
32+- *servant-client-core* Free `Client` implementation.
33+ Useful for testing `HasClient` instances.
34+ ([#920](https://github.com/haskell-servant/servant/pull/920))
35+
36+- *servant-client-core* Add `hoistClient` to `HasClient`.
37+ Just like `hoistServer` allows us to change the monad in which request handlers
38+ of a web application live in, we also have `hoistClient` for changing the monad
8− changing the monad in which client functions run.
39+ in which *client functions* live.
40+ Read [tutorial section for more information](https://haskell-servant.readthedocs.io/en/release-0.14/tutorial/Client.html#changing-the-monad-the-client-functions-live-in).
9 ([#936](https://github.com/haskell-servant/servant/pull/936))
41 ([#936](https://github.com/haskell-servant/servant/pull/936))
10
42
43+ iF you have own combinators, you'll need to define a new method of
44+ `HasClient` class, for example:
45+
46+ ```haskell
47+ type Client m (MyCombinator :> api) = MyValue :> Client m api
48+ hoistClientMonad pm _ nt cl = hoistClientMonad pm (Proxy :: Proxy api) nt . cl
49+ ```
50+
110.13.0.1
510.13.0.1
12--------
52--------
13
53
servant-client-core/servant-client-core.cabal+12−13
@@ -51,12 +51,11 @@ library
51 base >= 4.7 && < 4.12
51 base >= 4.7 && < 4.12
52 , bytestring >= 0.10.4.0 && < 0.11
52 , bytestring >= 0.10.4.0 && < 0.11
53 , containers >= 0.5.5.1 && < 0.6
53 , containers >= 0.5.5.1 && < 0.6
54− , mtl >= 2.1 && < 2.3
55 , text >= 1.2.3.0 && < 1.3
54 , text >= 1.2.3.0 && < 1.3
56
55
57 if !impl(ghc >= 8.0)
56 if !impl(ghc >= 8.0)
58 build-depends:
57 build-depends:
59− semigroups >=0.18.3 && <0.19
58+ semigroups >=0.18.4 && <0.19
60
59
61 -- Servant dependencies
60 -- Servant dependencies
62 build-depends:
61 build-depends:
@@ -65,16 +64,16 @@ library
65 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
64 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
66 -- Here can be exceptions if we really need features from the newer versions.
65 -- Here can be exceptions if we really need features from the newer versions.
67 build-depends:
66 build-depends:
68− base-compat >= 0.9.3 && < 0.11
67+ base-compat >= 0.10.1 && < 0.11
69 , base64-bytestring >= 1.0.0.1 && < 1.1
68 , base64-bytestring >= 1.0.0.1 && < 1.1
70− , exceptions >= 0.8.3 && < 0.11
69+ , exceptions >= 0.10.0 && < 0.11
71− , free >= 5.0.1 && < 5.1
70+ , free >= 5.0.2 && < 5.1
72− , generics-sop >= 0.3.1.0 && < 0.4
71+ , generics-sop >= 0.3.2.0 && < 0.4
73− , http-api-data >= 0.3.7.1 && < 0.4
72+ , http-api-data >= 0.3.8.1 && < 0.4
74− , http-media >= 0.7.1.1 && < 0.8
73+ , http-media >= 0.7.1.2 && < 0.8
75− , http-types >= 0.12 && < 0.13
74+ , http-types >= 0.12.1 && < 0.13
76 , network-uri >= 2.6.1.0 && < 2.7
75 , network-uri >= 2.6.1.0 && < 2.7
77− , safe >= 0.3.15 && < 0.4
76+ , safe >= 0.3.17 && < 0.4
78
77
79 hs-source-dirs: src
78 hs-source-dirs: src
80 default-language: Haskell2010
79 default-language: Haskell2010
@@ -99,8 +98,8 @@ test-suite spec
99 -- Additonal dependencies
98 -- Additonal dependencies
100 build-depends:
99 build-depends:
101 deepseq >= 1.3.0.2 && <1.5
100 deepseq >= 1.3.0.2 && <1.5
102− , hspec >= 2.4.4 && <2.6
101+ , hspec >= 2.4.1 && <2.6
103− , QuickCheck >= 2.10.1 && < 2.12
102+ , QuickCheck >= 2.11.3 && < 2.12
104
103
105 build-tool-depends:
104 build-tool-depends:
106− hspec-discover:hspec-discover >= 2.4.4 && <2.6
105+ hspec-discover:hspec-discover >= 2.5.1 && <2.6
servant-client-ghcjs/servant-client-ghcjs.cabal+1−1
@@ -42,7 +42,7 @@ library
42 , http-types >= 0.12 && < 0.13
42 , http-types >= 0.12 && < 0.13
43 , monad-control >= 1.0.0.4 && < 1.1
43 , monad-control >= 1.0.0.4 && < 1.1
44 , mtl >= 2.1 && < 2.3
44 , mtl >= 2.1 && < 2.3
45− , semigroupoids >= 4.3 && < 5.3
45+ , semigroupoids >= 4.3 && < 5.4
46 , servant-client-core == 0.14.*
46 , servant-client-core == 0.14.*
47 , string-conversions >= 0.3 && < 0.5
47 , string-conversions >= 0.3 && < 0.5
48 , transformers >= 0.3 && < 0.6
48 , transformers >= 0.3 && < 0.6
servant-client/CHANGELOG.md+37−2
@@ -4,10 +4,45 @@
40.14
40.14
5----
5----
6
6
7−- Add `hoistClient` for changing the monad in which
7+- `Stream` takes a status code argument
8+
9+ ```diff
10+ -Stream method framing ctype a
11+ +Stream method status framing ctype a
12+ ```
13+
14+ ([#966](https://github.com/haskell-servant/servant/pull/966)
15+ [#972](https://github.com/haskell-servant/servant/pull/972))
16+
17+- `ToStreamGenerator` definition changed, so it's possible to write an instance
18+ for conduits.
19+
20+ ```diff
21+ -class ToStreamGenerator f a where
22+ - toStreamGenerator :: f a -> StreamGenerator a
23+ +class ToStreamGenerator a b | a -> b where
24+ + toStreamGenerator :: a -> StreamGenerator b
25+ ```
26+
27+ ([#959](https://github.com/haskell-servant/servant/pull/959))
28+
29+- Added `NoFraming` streaming strategy
30+ ([#959](https://github.com/haskell-servant/servant/pull/959))
31+
32+- *servant-client-core* Add `hoistClient` to `HasClient`.
33+ Just like `hoistServer` allows us to change the monad in which request handlers
34+ of a web application live in, we also have `hoistClient` for changing the monad
8− client functions run.
35+ in which *client functions* live.
36+ Read [tutorial section for more information](https://haskell-servant.readthedocs.io/en/release-0.14/tutorial/Client.html#changing-the-monad-the-client-functions-live-in).
9 ([#936](https://github.com/haskell-servant/servant/pull/936))
37 ([#936](https://github.com/haskell-servant/servant/pull/936))
10
38
39+- *servant-client* Add more constructors to `RequestBody`, including
40+ `RequestBodyStream`.
41+ *Note:* we are looking for http-library agnostic API,
42+ so the might change again soon.
43+ Tell us which constructors are useful for you!
44+ ([#913](https://github.com/haskell-servant/servant/pull/913))
45+
110.13.0.1
460.13.0.1
12--------
47--------
13
48
servant-client/servant-client.cabal+17−23
@@ -51,7 +51,7 @@ library
51 , transformers >= 0.3.0.0 && < 0.6
51 , transformers >= 0.3.0.0 && < 0.6
52
52
53 if !impl(ghc >= 8.0)
53 if !impl(ghc >= 8.0)
54− build-depends: semigroups >=0.18.3 && <0.19
54+ build-depends: semigroups >=0.18.4 && <0.19
55
55
56 -- Servant dependencies
56 -- Servant dependencies
57 build-depends:
57 build-depends:
@@ -60,19 +60,16 @@ library
60 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
60 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
61 -- Here can be exceptions if we really need features from the newer versions.
61 -- Here can be exceptions if we really need features from the newer versions.
62 build-depends:
62 build-depends:
63− aeson >= 1.2.3.0 && < 1.5
64− , base-compat >= 0.9.3 && < 0.11
63+ base-compat >= 0.10.1 && < 0.11
65− , attoparsec >= 0.13.2.0 && < 0.14
66− , http-client >= 0.5.7.1 && < 0.6
64+ , http-client >= 0.5.12 && < 0.6
67− , http-client-tls >= 0.3.5.1 && < 0.4
68− , http-media >= 0.7.1.1 && < 0.8
65+ , http-media >= 0.7.1.2 && < 0.8
69− , http-types >= 0.12 && < 0.13
66+ , http-types >= 0.12.1 && < 0.13
70− , exceptions >= 0.8.3 && < 0.11
67+ , exceptions >= 0.10.0 && < 0.11
71− , monad-control >= 1.0.0.4 && < 1.1
68+ , monad-control >= 1.0.2.3 && < 1.1
72− , semigroupoids >= 5.2.1 && < 5.3
69+ , semigroupoids >= 5.2.2 && < 5.4
73− , stm >= 2.4.4.1 && < 2.5
70+ , stm >= 2.4.5.0 && < 2.5
74− , transformers-base >= 0.4.4 && < 0.5
71+ , transformers-base >= 0.4.5.2 && < 0.5
75− , transformers-compat >= 0.5.1 && < 0.7
72+ , transformers-compat >= 0.6.2 && < 0.7
76
73
77 hs-source-dirs: src
74 hs-source-dirs: src
78 default-language: Haskell2010
75 default-language: Haskell2010
@@ -97,10 +94,8 @@ test-suite spec
97 , aeson
94 , aeson
98 , base-compat
95 , base-compat
99 , bytestring
96 , bytestring
100− , containers
101 , http-api-data
97 , http-api-data
102 , http-client
98 , http-client
103− , http-media
104 , http-types
99 , http-types
105 , mtl
100 , mtl
106 , servant-client
101 , servant-client
@@ -117,18 +112,16 @@ test-suite spec
117
112
118 -- Additonal dependencies
113 -- Additonal dependencies
119 build-depends:
114 build-depends:
120− deepseq >= 1.3.0.2 && < 1.5
121− , generics-sop >= 0.3.1.0 && < 0.4
115+ generics-sop >= 0.3.2.0 && < 0.4
122− , hspec >= 2.4.4 && < 2.6
116+ , hspec >= 2.5.1 && < 2.6
123 , HUnit >= 1.6 && < 1.7
117 , HUnit >= 1.6 && < 1.7
124− , random-bytestring >= 0.1 && < 0.2
125 , network >= 2.6.3.2 && < 2.8
118 , network >= 2.6.3.2 && < 2.8
126 , QuickCheck >= 2.10.1 && < 2.12
119 , QuickCheck >= 2.10.1 && < 2.12
127− , servant == 0.13.*
120+ , servant == 0.14.*
128− , servant-server == 0.13.*
121+ , servant-server == 0.14.*
129
122
130 build-tool-depends:
123 build-tool-depends:
131− hspec-discover:hspec-discover >= 2.4.4 && < 2.6
124+ hspec-discover:hspec-discover >= 2.5.1 && < 2.6
132
125
133test-suite readme
126test-suite readme
134 type: exitcode-stdio-1.0
127 type: exitcode-stdio-1.0
@@ -136,3 +129,4 @@ test-suite readme
136 build-depends: base, servant, http-client, text, servant-client, markdown-unlit
129 build-depends: base, servant, http-client, text, servant-client, markdown-unlit
137 build-tool-depends: markdown-unlit:markdown-unlit
130 build-tool-depends: markdown-unlit:markdown-unlit
138 ghc-options: -pgmL markdown-unlit
131 ghc-options: -pgmL markdown-unlit
132+ default-language: Haskell2010
servant-client/test/Servant/StreamSpec.hs+2
@@ -130,6 +130,7 @@ streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
130 jrb = Just (Right bob)
130 jrb = Just (Right bob)
131 runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
131 runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
132
132
133+{-
133 it "streams in constant memory" $ \(_, baseUrl) -> do
134 it "streams in constant memory" $ \(_, baseUrl) -> do
134 Right (ResultStream res) <- runClient getGetALot baseUrl
135 Right (ResultStream res) <- runClient getGetALot baseUrl
135 let consumeNChunks n = replicateM_ n (res void)
136 let consumeNChunks n = replicateM_ n (res void)
@@ -140,6 +141,7 @@ streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
140 memUsed <- currentBytesUsed <$> getGCStats
141 memUsed <- currentBytesUsed <$> getGCStats
141#endif
142#endif
142 memUsed `shouldSatisfy` (< megabytes 22)
143 memUsed `shouldSatisfy` (< megabytes 22)
144+-}
143
145
144megabytes :: Num a => a -> a
146megabytes :: Num a => a -> a
145megabytes n = n * (1000 ^ (2 :: Int))
147megabytes n = n * (1000 ^ (2 :: Int))
servant-docs/servant-docs.cabal+1−1
@@ -64,7 +64,7 @@ library
64 , case-insensitive >= 1.2.0.10 && < 1.3
64 , case-insensitive >= 1.2.0.10 && < 1.3
65 , control-monad-omega >= 0.3.1 && < 0.4
65 , control-monad-omega >= 0.3.1 && < 0.4
66 , hashable >= 1.2.6.1 && < 1.3
66 , hashable >= 1.2.6.1 && < 1.3
67− , http-media >= 0.7.1.1 && < 0.8
67+ , http-media >= 0.7.0 && < 0.8
68 , http-types >= 0.12 && < 0.13
68 , http-types >= 0.12 && < 0.13
69 , lens >= 4.15.4 && < 4.17
69 , lens >= 4.15.4 && < 4.17
70 , string-conversions >= 0.4.0.1 && < 0.5
70 , string-conversions >= 0.4.0.1 && < 0.5
servant-foreign/servant-foreign.cabal+1
@@ -1,5 +1,6 @@
1name: servant-foreign
1name: servant-foreign
2version: 0.11.1
2version: 0.11.1
3+x-revision: 2
3synopsis: Helpers for generating clients for servant APIs in any programming language
4synopsis: Helpers for generating clients for servant APIs in any programming language
4description:
5description:
5 Helper types and functions for generating client functions for servant APIs in any programming language
6 Helper types and functions for generating client functions for servant APIs in any programming language
servant-server/CHANGELOG.md+46
@@ -1,6 +1,52 @@
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md)
1[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
2[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
3
3
4+0.14
5+----
6+
7+- `Stream` takes a status code argument
8+
9+ ```diff
10+ -Stream method framing ctype a
11+ +Stream method status framing ctype a
12+ ```
13+
14+ ([#966](https://github.com/haskell-servant/servant/pull/966)
15+ [#972](https://github.com/haskell-servant/servant/pull/972))
16+
17+- `ToStreamGenerator` definition changed, so it's possible to write an instance
18+ for conduits.
19+
20+ ```diff
21+ -class ToStreamGenerator f a where
22+ - toStreamGenerator :: f a -> StreamGenerator a
23+ +class ToStreamGenerator a b | a -> b where
24+ + toStreamGenerator :: a -> StreamGenerator b
25+ ```
26+
27+ ([#959](https://github.com/haskell-servant/servant/pull/959))
28+
29+- Added `NoFraming` streaming strategy
30+ ([#959](https://github.com/haskell-servant/servant/pull/959))
31+
32+- *servant-server* File serving in polymorphic monad.
33+ i.e. Generalised types of `serveDirectoryFileServer` etc functions in
34+ `Servant.Utils.StaticFiles`
35+ ([#953](https://github.com/haskell-servant/servant/pull/953))
36+
37+- *servant-server* `ReqBody` content type check is recoverable.
38+ This allows writing APIs like:
39+
40+ ```haskell
41+ ReqBody '[JSON] Int :> Post '[PlainText] Int
42+ :<|> ReqBody '[PlainText] Int :> Post '[PlainText] Int
43+ ```
44+
45+ which is useful when handlers are subtly different,
46+ for example may do less work.
47+ ([#937](https://github.com/haskell-servant/servant/pull/937))
48+
49+
40.13.0.1
500.13.0.1
5--------
51--------
6
52
servant-server/servant-server.cabal+23−31
@@ -40,7 +40,7 @@ custom-setup
40 setup-depends:
40 setup-depends:
41 base >= 4 && <5,
41 base >= 4 && <5,
42 Cabal,
42 Cabal,
43− cabal-doctest >= 1.0.1 && <1.1
43+ cabal-doctest >= 1.0.6 && <1.1
44
44
45library
45library
46 exposed-modules:
46 exposed-modules:
@@ -71,7 +71,7 @@ library
71
71
72 if !impl(ghc >= 8.0)
72 if !impl(ghc >= 8.0)
73 build-depends:
73 build-depends:
74− semigroups >= 0.18.3 && < 0.19
74+ semigroups >= 0.18.4 && < 0.19
75
75
76 -- Servant dependencies
76 -- Servant dependencies
77 build-depends:
77 build-depends:
@@ -80,28 +80,22 @@ library
80 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
80 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
81 -- Here can be exceptions if we really need features from the newer versions.
81 -- Here can be exceptions if we really need features from the newer versions.
82 build-depends:
82 build-depends:
83− aeson >= 1.2.3.0 && < 1.5
84− , base-compat >= 0.9.3 && < 0.11
83+ base-compat >= 0.10.1 && < 0.11
85− , attoparsec >= 0.13.2.0 && < 0.14
86 , base64-bytestring >= 1.0.0.1 && < 1.1
84 , base64-bytestring >= 1.0.0.1 && < 1.1
87− , exceptions >= 0.8.3 && < 0.11
85+ , exceptions >= 0.10.0 && < 0.11
88− , http-api-data >= 0.3.7.1 && < 0.4
86+ , http-api-data >= 0.3.8.1 && < 0.4
89− , http-media >= 0.7.1.1 && < 0.8
87+ , http-media >= 0.7.1.2 && < 0.8
90− , http-types >= 0.12 && < 0.13
88+ , http-types >= 0.12.1 && < 0.13
91 , network-uri >= 2.6.1.0 && < 2.7
89 , network-uri >= 2.6.1.0 && < 2.7
92− , monad-control >= 1.0.0.4 && < 1.1
90+ , monad-control >= 1.0.2.3 && < 1.1
93− , network >= 2.6.3.2 && < 2.8
91+ , network >= 2.6.3.5 && < 2.8
94− , safe >= 0.3.15 && < 0.4
95− , split >= 0.2.3.2 && < 0.3
96 , string-conversions >= 0.4.0.1 && < 0.5
92 , string-conversions >= 0.4.0.1 && < 0.5
97− , system-filepath >= 0.4 && < 0.5
98− , resourcet >= 1.1.9 && < 1.3
93+ , resourcet >= 1.1.11 && < 1.3
99 , tagged >= 0.8.5 && < 0.9
94 , tagged >= 0.8.5 && < 0.9
100 , transformers-base >= 0.4.4 && < 0.5
95 , transformers-base >= 0.4.4 && < 0.5
101− , transformers-compat >= 0.5.1 && < 0.7
96+ , transformers-compat >= 0.6.2 && < 0.7
102 , wai >= 3.2.1.1 && < 3.3
97 , wai >= 3.2.1.1 && < 3.3
103 , wai-app-static >= 3.1.6.1 && < 3.2
98 , wai-app-static >= 3.1.6.1 && < 3.2
104− , warp >= 3.2.13 && < 3.3
105 , word8 >= 0.1.3 && < 0.2
99 , word8 >= 0.1.3 && < 0.2
106
100
107 hs-source-dirs: src
101 hs-source-dirs: src
@@ -120,11 +114,13 @@ executable greet
120 base
114 base
121 , servant
115 , servant
122 , servant-server
116 , servant-server
123− , aeson
124− , warp
125 , wai
117 , wai
126 , text
118 , text
127
119
120+ build-depends:
121+ aeson >= 1.3.1.1 && < 1.5
122+ , warp >= 3.2.13 && < 3.3
123+
128test-suite spec
124test-suite spec
129 type: exitcode-stdio-1.0
125 type: exitcode-stdio-1.0
130 ghc-options: -Wall
126 ghc-options: -Wall
@@ -148,13 +144,10 @@ test-suite spec
148 build-depends:
144 build-depends:
149 base
145 base
150 , base-compat
146 , base-compat
151− , aeson
152 , base64-bytestring
147 , base64-bytestring
153 , bytestring
148 , bytestring
154− , exceptions
155 , http-types
149 , http-types
156 , mtl
150 , mtl
157− , network
158 , resourcet
151 , resourcet
159 , safe
152 , safe
160 , servant
153 , servant
@@ -164,27 +157,26 @@ test-suite spec
164 , transformers
157 , transformers
165 , transformers-compat
158 , transformers-compat
166 , wai
159 , wai
167− , warp
168
160
169 -- Additonal dependencies
161 -- Additonal dependencies
170 build-depends:
162 build-depends:
163+ aeson >= 1.3.1.1 && < 1.5
171− directory >= 1.2.1.0 && < 1.4
164+ , directory >= 1.2.1.0 && < 1.4
172− , hspec >= 2.4.4 && < 2.6
165+ , hspec >= 2.5.1 && < 2.6
173− , hspec-wai >= 0.9 && < 0.10
166+ , hspec-wai >= 0.9.0 && < 0.10
167+ , QuickCheck >= 2.11.3 && < 2.12
174 , should-not-typecheck >= 2.1.0 && < 2.2
168 , should-not-typecheck >= 2.1.0 && < 2.2
175− , parsec >= 3.1.11 && < 3.2
169+ , temporary >= 1.3 && < 1.4
176− , QuickCheck >= 2.10.1 && < 2.12
177 , wai-extra >= 3.0.21.0 && < 3.1
170 , wai-extra >= 3.0.21.0 && < 3.1
178− , temporary >= 1.2.0.3 && < 1.4
179
171
180 build-tool-depends:
172 build-tool-depends:
181− hspec-discover:hspec-discover >=2.4.4 && <2.6
173+ hspec-discover:hspec-discover >= 2.5.1 && <2.6
182
174
183test-suite doctests
175test-suite doctests
184 build-depends:
176 build-depends:
185 base
177 base
186 , servant-server
178 , servant-server
187− , doctest >= 0.13.0 && <0.16
179+ , doctest >= 0.15.0 && <0.17
188 type: exitcode-stdio-1.0
180 type: exitcode-stdio-1.0
189 main-is: test/doctests.hs
181 main-is: test/doctests.hs
190 buildable: True
182 buildable: True
servant-server/src/Servant.hs+2−2
@@ -6,7 +6,7 @@ module Servant (
6 -- | For implementing servers for servant APIs.
6 -- | For implementing servers for servant APIs.
7 module Servant.Server,
7 module Servant.Server,
8 -- | Utilities on top of the servant core
8 -- | Utilities on top of the servant core
9− module Servant.Utils.Links,
9+ module Servant.Links,
10 module Servant.Utils.StaticFiles,
10 module Servant.Utils.StaticFiles,
11 -- | Useful re-exports
11 -- | Useful re-exports
12 Proxy(..),
12 Proxy(..),
@@ -17,5 +17,5 @@ import Control.Monad.Error.Class (throwError)
17import Data.Proxy
17import Data.Proxy
18import Servant.API
18import Servant.API
19import Servant.Server
19import Servant.Server
20−import Servant.Utils.Links
20+import Servant.Links
21import Servant.Utils.StaticFiles
21import Servant.Utils.StaticFiles
servant-server/src/Servant/Server/Internal/Context.hs+1−1
@@ -18,7 +18,7 @@ import GHC.TypeLits
18-- | 'Context's are used to pass values to combinators. (They are __not__ meant
18-- | 'Context's are used to pass values to combinators. (They are __not__ meant
19-- to be used to pass parameters to your handlers, i.e. they should not replace
19-- to be used to pass parameters to your handlers, i.e. they should not replace
20-- any custom 'Control.Monad.Trans.Reader.ReaderT'-monad-stack that you're using
20-- any custom 'Control.Monad.Trans.Reader.ReaderT'-monad-stack that you're using
21−-- with 'Servant.Utils.Enter'.) If you don't use combinators that
21+-- with 'hoistServer'.) If you don't use combinators that
22-- require any context entries, you can just use 'Servant.Server.serve' as always.
22-- require any context entries, you can just use 'Servant.Server.serve' as always.
23--
23--
24-- If you are using combinators that require a non-empty 'Context' you have to
24-- If you are using combinators that require a non-empty 'Context' you have to
servant/CHANGELOG.md+14−5
@@ -30,16 +30,25 @@
30- Added `NoFraming` streaming strategy
30- Added `NoFraming` streaming strategy
31 ([#959](https://github.com/haskell-servant/servant/pull/959))
31 ([#959](https://github.com/haskell-servant/servant/pull/959))
32
32
33−- *servant-client* Free `Client` implementation.
33+- *servant-client-core* Free `Client` implementation.
34 Useful for testing `HasClient` instances.
34 Useful for testing `HasClient` instances.
35 ([#920](https://github.com/haskell-servant/servant/pull/920))
35 ([#920](https://github.com/haskell-servant/servant/pull/920))
36
36
37−- *servant-client* Add `hoistClient` to `HasClient`.
37+- *servant-client-core* Add `hoistClient` to `HasClient`.
38 Just like `hoistServer` allows us to change the monad in which request handlers
38 Just like `hoistServer` allows us to change the monad in which request handlers
39 of a web application live in, we also have `hoistClient` for changing the monad
39 of a web application live in, we also have `hoistClient` for changing the monad
40− in which *client functions* live. Read [tutorial section for more information](#link).
40+ in which *client functions* live.
41+ Read [tutorial section for more information](https://haskell-servant.readthedocs.io/en/release-0.14/tutorial/Client.html#changing-the-monad-the-client-functions-live-in).
41 ([#936](https://github.com/haskell-servant/servant/pull/936))
42 ([#936](https://github.com/haskell-servant/servant/pull/936))
42
43
44+ iF you have own combinators, you'll need to define a new method of
45+ `HasClient` class, for example:
46+
47+ ```haskell
48+ type Client m (MyCombinator :> api) = MyValue :> Client m api
49+ hoistClientMonad pm _ nt cl = hoistClientMonad pm (Proxy :: Proxy api) nt . cl
50+ ```
51+
43- *servant* Add `safeLink' :: (Link -> a) -> ... -> MkLink endpoint a`,
52- *servant* Add `safeLink' :: (Link -> a) -> ... -> MkLink endpoint a`,
44 which allows to create helpers returning something else than `Link`.
53 which allows to create helpers returning something else than `Link`.
45 ([#968](https://github.com/haskell-servant/servant/pull/968))
54 ([#968](https://github.com/haskell-servant/servant/pull/968))
@@ -76,10 +85,10 @@
76- Added tests or enabled tests
85- Added tests or enabled tests
77 ([#975](https://github.com/haskell-servant/servant/pull/975))
86 ([#975](https://github.com/haskell-servant/servant/pull/975))
78
87
79−- Add [pagination cookbook recipe](#link)
88+- Add [pagination cookbook recipe](https://haskell-servant.readthedocs.io/en/release-0.14/cookbook/pagination/Pagination.html)
80 ([#946](https://github.com/haskell-servant/servant/pull/946))
89 ([#946](https://github.com/haskell-servant/servant/pull/946))
81
90
82−- Add [`servant-flatten` cookbook recipe](#link)
91+- Add [`servant-flatten` "spice" to the structuring api recipe](https://haskell-servant.readthedocs.io/en/release-0.14/cookbook/structuring-apis/StructuringApis.html)
83 ([#929](https://github.com/haskell-servant/servant/pull/929))
92 ([#929](https://github.com/haskell-servant/servant/pull/929))
84
93
85- Dependency updates
94- Dependency updates
servant/servant.cabal+24−21
@@ -34,7 +34,7 @@ custom-setup
34 setup-depends:
34 setup-depends:
35 base >= 4 && <5,
35 base >= 4 && <5,
36 Cabal,
36 Cabal,
37− cabal-doctest >= 1.0.2 && <1.1
37+ cabal-doctest >= 1.0.6 && <1.1
38
38
39library
39library
40 exposed-modules:
40 exposed-modules:
@@ -62,6 +62,10 @@ library
62 Servant.API.Vault
62 Servant.API.Vault
63 Servant.API.Verbs
63 Servant.API.Verbs
64 Servant.API.WithNamedContext
64 Servant.API.WithNamedContext
65+ Servant.Links
66+
67+ -- Deprecated modules, to be removed in late 2019
68+ exposed-modules:
65 Servant.Utils.Links
69 Servant.Utils.Links
66 Servant.Utils.Enter
70 Servant.Utils.Enter
67
71
@@ -77,25 +81,25 @@ library
77
81
78 if !impl(ghc >= 8.0)
82 if !impl(ghc >= 8.0)
79 build-depends:
83 build-depends:
80− semigroups >= 0.18.3 && < 0.19
84+ semigroups >= 0.18.4 && < 0.19
81
85
82 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
86 -- Other dependencies: Lower bound around what is in the latest Stackage LTS.
83 -- Here can be exceptions if we really need features from the newer versions.
87 -- Here can be exceptions if we really need features from the newer versions.
84 build-depends:
88 build-depends:
85− base-compat >= 0.9.3 && < 0.11
89+ base-compat >= 0.10.1 && < 0.11
86− , aeson >= 1.2.3.0 && < 1.5
90+ , aeson >= 1.3.1.1 && < 1.5
87− , attoparsec >= 0.13.2.0 && < 0.14
91+ , attoparsec >= 0.13.2.2 && < 0.14
88 , case-insensitive >= 1.2.0.10 && < 1.3
92 , case-insensitive >= 1.2.0.10 && < 1.3
89− , http-api-data >= 0.3.7.1 && < 0.4
93+ , http-api-data >= 0.3.8.1 && < 0.4
90− , http-media >= 0.7.1.1 && < 0.8
94+ , http-media >= 0.7.1.2 && < 0.8
91− , http-types >= 0.12 && < 0.13
95+ , http-types >= 0.12.1 && < 0.13
92 , natural-transformation >= 0.4 && < 0.5
96 , natural-transformation >= 0.4 && < 0.5
93− , mmorph >= 1.1.0 && < 1.2
97+ , mmorph >= 1.1.2 && < 1.2
94 , tagged >= 0.8.5 && < 0.9
98 , tagged >= 0.8.5 && < 0.9
95− , singleton-bool >= 0.1.2.0 && < 0.2
99+ , singleton-bool >= 0.1.4 && < 0.2
96 , string-conversions >= 0.4.0.1 && < 0.5
100 , string-conversions >= 0.4.0.1 && < 0.5
97 , network-uri >= 2.6.1.0 && < 2.7
101 , network-uri >= 2.6.1.0 && < 2.7
98− , vault >= 0.3.0.7 && < 0.4
102+ , vault >= 0.3.1.1 && < 0.4
99
103
100 hs-source-dirs: src
104 hs-source-dirs: src
101 default-language: Haskell2010
105 default-language: Haskell2010
@@ -133,15 +137,14 @@ test-suite spec
133 other-modules:
137 other-modules:
134 Servant.API.ContentTypesSpec
138 Servant.API.ContentTypesSpec
135 Servant.API.ResponseHeadersSpec
139 Servant.API.ResponseHeadersSpec
136− Servant.Utils.LinksSpec
137 Servant.Utils.EnterSpec
140 Servant.Utils.EnterSpec
141+ Servant.LinksSpec
138
142
139 -- Dependencies inherited from the library. No need to specify bounds.
143 -- Dependencies inherited from the library. No need to specify bounds.
140 build-depends:
144 build-depends:
141 base
145 base
142 , base-compat
146 , base-compat
143 , aeson
147 , aeson
144− , attoparsec
145 , bytestring
148 , bytestring
146 , servant
149 , servant
147 , string-conversions
150 , string-conversions
@@ -153,23 +156,23 @@ test-suite spec
153
156
154 -- Additonal dependencies
157 -- Additonal dependencies
155 build-depends:
158 build-depends:
156− aeson-compat >= 0.3.3 && < 0.4
159+ aeson-compat >= 0.3.7.1 && < 0.4
157− , hspec >= 2.4.4 && < 2.6
160+ , hspec >= 2.5.1 && < 2.6
158− , QuickCheck >= 2.10.1 && < 2.12
161+ , QuickCheck >= 2.11.3 && < 2.12
159− , quickcheck-instances >= 0.3.16 && < 0.4
162+ , quickcheck-instances >= 0.3.18 && < 0.4
160
163
161 build-tool-depends:
164 build-tool-depends:
162− hspec-discover:hspec-discover >= 2.4.4 && < 2.6
165+ hspec-discover:hspec-discover >= 2.5.1 && < 2.6
163
166
164test-suite doctests
167test-suite doctests
165 build-depends:
168 build-depends:
166 base
169 base
167 , servant
170 , servant
168− , doctest >= 0.13.0 && <0.16
171+ , doctest >= 0.15.0 && <0.17
169
172
170 -- We test Links failure with doctest, so we need extra dependencies
173 -- We test Links failure with doctest, so we need extra dependencies
171 build-depends:
174 build-depends:
172− hspec >= 2.4.4 && < 2.6
175+ hspec >= 2.5.1 && < 2.6
173
176
174 type: exitcode-stdio-1.0
177 type: exitcode-stdio-1.0
175 main-is: test/doctests.hs
178 main-is: test/doctests.hs
@@ -180,4 +183,4 @@ test-suite doctests
180 x-doctest-options: -fdiagnostics-color=never
183 x-doctest-options: -fdiagnostics-color=never
181 include-dirs: include
184 include-dirs: include
182 x-doctest-source-dirs: test
185 x-doctest-source-dirs: test
183− x-doctest-modules: Servant.Utils.LinksSpec
186+ x-doctest-modules: Servant.LinksSpec
servant/src/Servant/API.hs+3−3
@@ -63,8 +63,8 @@ module Servant.API (
63 module Servant.API.Experimental.Auth,
63 module Servant.API.Experimental.Auth,
64 -- | General Authentication
64 -- | General Authentication
65
65
66− -- * Utilities
66+ -- * Links
67− module Servant.Utils.Links,
67+ module Servant.Links,
68 -- | Type-safe internal URIs
68 -- | Type-safe internal URIs
69
69
70 -- * Re-exports
70 -- * Re-exports
@@ -134,7 +134,7 @@ import Servant.API.Verbs
134 ReflectMethod (reflectMethod), StdMethod (..), Verb)
134 ReflectMethod (reflectMethod), StdMethod (..), Verb)
135import Servant.API.WithNamedContext
135import Servant.API.WithNamedContext
136 (WithNamedContext)
136 (WithNamedContext)
137−import Servant.Utils.Links
137+import Servant.Links
138 (HasLink (..), IsElem, IsElem', Link, URI (..), safeLink)
138 (HasLink (..), IsElem, IsElem', Link, URI (..), safeLink)
139import Web.HttpApiData
139import Web.HttpApiData
140 (FromHttpApiData (..), ToHttpApiData (..))
140 (FromHttpApiData (..), ToHttpApiData (..))
servant/src/Servant/API/Verbs.hs+1−1
@@ -58,7 +58,7 @@ type Patch = Verb 'PATCH 200
58--
58--
59-- If the resource cannot be created immediately, use 'PostAccepted'.
59-- If the resource cannot be created immediately, use 'PostAccepted'.
60--
60--
61−-- Consider using 'Servant.Utils.Links.safeLink' for the @Location@ header
61+-- Consider using 'Servant.Links.safeLink' for the @Location@ header
62-- field.
62-- field.
63
63
64-- | 'POST' with 201 status code.
64-- | 'POST' with 201 status code.
servant/src/Servant/Links.hsnew file+487
@@ -0,0 +1,487 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE FunctionalDependencies #-}
6+{-# LANGUAGE PolyKinds #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE TypeOperators #-}
10+{-# LANGUAGE UndecidableInstances #-}
11+{-# OPTIONS_HADDOCK not-home #-}
12+
13+-- | Type safe generation of internal links.
14+--
15+-- Given an API with a few endpoints:
16+--
17+-- >>> :set -XDataKinds -XTypeFamilies -XTypeOperators
18+-- >>> import Servant.API
19+-- >>> import Servant.Links
20+-- >>> import Data.Proxy
21+-- >>>
22+-- >>> type Hello = "hello" :> Get '[JSON] Int
23+-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete '[JSON] NoContent
24+-- >>> type API = Hello :<|> Bye
25+-- >>> let api = Proxy :: Proxy API
26+--
27+-- It is possible to generate links that are guaranteed to be within 'API' with
28+-- 'safeLink'. The first argument to 'safeLink' is a type representing the API
29+-- you would like to restrict links to. The second argument is the destination
30+-- endpoint you would like the link to point to, this will need to end with a
31+-- verb like GET or POST. Further arguments may be required depending on the
32+-- type of the endpoint. If everything lines up you will get a 'Link' out the
33+-- other end.
34+--
35+-- You may omit 'QueryParam's and the like should you not want to provide them,
36+-- but types which form part of the URL path like 'Capture' must be included.
37+-- The reason you may want to omit 'QueryParam's is that safeLink is a bit
38+-- magical: if parameters are included that could take input it will return a
39+-- function that accepts that input and generates a link. This is best shown
40+-- with an example. Here, a link is generated with no parameters:
41+--
42+-- >>> let hello = Proxy :: Proxy ("hello" :> Get '[JSON] Int)
43+-- >>> toUrlPiece (safeLink api hello :: Link)
44+-- "hello"
45+--
46+-- If the API has an endpoint with parameters then we can generate links with
47+-- or without those:
48+--
49+-- >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete '[JSON] NoContent)
50+-- >>> toUrlPiece $ safeLink api with (Just "Hubert")
51+-- "bye?name=Hubert"
52+--
53+-- >>> let without = Proxy :: Proxy ("bye" :> Delete '[JSON] NoContent)
54+-- >>> toUrlPiece $ safeLink api without
55+-- "bye"
56+--
57+-- If you would like create a helper for generating links only within that API,
58+-- you can partially apply safeLink if you specify a correct type signature
59+-- like so:
60+--
61+-- >>> :set -XConstraintKinds
62+-- >>> :{
63+-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
64+-- >>> => Proxy endpoint -> MkLink endpoint Link
65+-- >>> apiLink = safeLink api
66+-- >>> :}
67+--
68+-- `safeLink'` allows to make specialise the output:
69+--
70+-- >>> safeLink' toUrlPiece api without
71+-- "bye"
72+--
73+-- >>> :{
74+-- >>> let apiTextLink :: (IsElem endpoint API, HasLink endpoint)
75+-- >>> => Proxy endpoint -> MkLink endpoint Text
76+-- >>> apiTextLink = safeLink' toUrlPiece api
77+-- >>> :}
78+--
79+-- >>> apiTextLink without
80+-- "bye"
81+--
82+-- Attempting to construct a link to an endpoint that does not exist in api
83+-- will result in a type error like this:
84+--
85+-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] NoContent)
86+-- >>> safeLink api bad_link
87+-- ...
88+-- ...Could not deduce...
89+-- ...
90+--
91+-- This error is essentially saying that the type family couldn't find
92+-- bad_link under api after trying the open (but empty) type family
93+-- `IsElem'` as a last resort.
94+module Servant.Links (
95+ module Servant.API.TypeLevel,
96+
97+ -- * Building and using safe links
98+ --
99+ -- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
100+ safeLink
101+ , safeLink'
102+ , allLinks
103+ , allLinks'
104+ , URI(..)
105+ -- * Adding custom types
106+ , HasLink(..)
107+ , Link
108+ , linkURI
109+ , linkURI'
110+ , LinkArrayElementStyle (..)
111+ -- ** Link accessors
112+ , Param (..)
113+ , linkSegments
114+ , linkQueryParams
115+) where
116+
117+import Data.List
118+import Data.Proxy
119+ (Proxy (..))
120+import Data.Semigroup
121+ ((<>))
122+import Data.Singletons.Bool
123+ (SBool (..), SBoolI (..))
124+import qualified Data.Text as Text
125+import qualified Data.Text.Encoding as TE
126+import Data.Type.Bool
127+ (If)
128+import GHC.TypeLits
129+ (KnownSymbol, symbolVal)
130+import Network.URI
131+ (URI (..), escapeURIString, isUnreserved)
132+import Prelude ()
133+import Prelude.Compat
134+
135+import Servant.API.Alternative
136+ ((:<|>) ((:<|>)))
137+import Servant.API.BasicAuth
138+ (BasicAuth)
139+import Servant.API.Capture
140+ (Capture', CaptureAll)
141+import Servant.API.Description
142+ (Description, Summary)
143+import Servant.API.Empty
144+ (EmptyAPI (..))
145+import Servant.API.Experimental.Auth
146+ (AuthProtect)
147+import Servant.API.Header
148+ (Header')
149+import Servant.API.HttpVersion
150+ (HttpVersion)
151+import Servant.API.IsSecure
152+ (IsSecure)
153+import Servant.API.Modifiers
154+ (FoldRequired)
155+import Servant.API.QueryParam
156+ (QueryFlag, QueryParam', QueryParams)
157+import Servant.API.Raw
158+ (Raw)
159+import Servant.API.RemoteHost
160+ (RemoteHost)
161+import Servant.API.ReqBody
162+ (ReqBody')
163+import Servant.API.Stream
164+ (Stream)
165+import Servant.API.Sub
166+ (type (:>))
167+import Servant.API.TypeLevel
168+import Servant.API.Vault
169+ (Vault)
170+import Servant.API.Verbs
171+ (Verb)
172+import Servant.API.WithNamedContext
173+ (WithNamedContext)
174+import Web.HttpApiData
175+
176+-- | A safe link datatype.
177+-- The only way of constructing a 'Link' is using 'safeLink', which means any
178+-- 'Link' is guaranteed to be part of the mentioned API.
179+data Link = Link
180+ { _segments :: [Escaped]
181+ , _queryParams :: [Param]
182+ } deriving Show
183+
184+newtype Escaped = Escaped String
185+
186+escaped :: String -> Escaped
187+escaped = Escaped . escapeURIString isUnreserved
188+
189+getEscaped :: Escaped -> String
190+getEscaped (Escaped s) = s
191+
192+instance Show Escaped where
193+ showsPrec d (Escaped s) = showsPrec d s
194+ show (Escaped s) = show s
195+
196+linkSegments :: Link -> [String]
197+linkSegments = map getEscaped . _segments
198+
199+linkQueryParams :: Link -> [Param]
200+linkQueryParams = _queryParams
201+
202+instance ToHttpApiData Link where
203+ toHeader = TE.encodeUtf8 . toUrlPiece
204+ toUrlPiece l =
205+ let uri = linkURI l
206+ in Text.pack $ uriPath uri ++ uriQuery uri
207+
208+-- | Query parameter.
209+data Param
210+ = SingleParam String Text.Text
211+ | ArrayElemParam String Text.Text
212+ | FlagParam String
213+ deriving Show
214+
215+addSegment :: Escaped -> Link -> Link
216+addSegment seg l = l { _segments = _segments l <> [seg] }
217+
218+addQueryParam :: Param -> Link -> Link
219+addQueryParam qp l =
220+ l { _queryParams = _queryParams l <> [qp] }
221+
222+-- | Transform 'Link' into 'URI'.
223+--
224+-- >>> type API = "something" :> Get '[JSON] Int
225+-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
226+-- something
227+--
228+-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
229+-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
230+-- sum?x[]=1&x[]=2&x[]=3
231+--
232+-- >>> type API = "foo/bar" :> Get '[JSON] Int
233+-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
234+-- foo%2Fbar
235+--
236+-- >>> type SomeRoute = "abc" :> Capture "email" String :> Put '[JSON] ()
237+-- >>> let someRoute = Proxy :: Proxy SomeRoute
238+-- >>> safeLink someRoute someRoute "test@example.com"
239+-- Link {_segments = ["abc","test%40example.com"], _queryParams = []}
240+--
241+-- >>> linkURI $ safeLink someRoute someRoute "test@example.com"
242+-- abc/test%40example.com
243+--
244+linkURI :: Link -> URI
245+linkURI = linkURI' LinkArrayElementBracket
246+
247+-- | How to encode array query elements.
248+data LinkArrayElementStyle
249+ = LinkArrayElementBracket -- ^ @foo[]=1&foo[]=2@
250+ | LinkArrayElementPlain -- ^ @foo=1&foo=2@
251+ deriving (Eq, Ord, Show, Enum, Bounded)
252+
253+-- | Configurable 'linkURI'.
254+--
255+-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
256+-- >>> linkURI' LinkArrayElementBracket $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
257+-- sum?x[]=1&x[]=2&x[]=3
258+--
259+-- >>> linkURI' LinkArrayElementPlain $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
260+-- sum?x=1&x=2&x=3
261+--
262+linkURI' :: LinkArrayElementStyle -> Link -> URI
263+linkURI' addBrackets (Link segments q_params) =
264+ URI mempty -- No scheme (relative)
265+ Nothing -- Or authority (relative)
266+ (intercalate "/" $ map getEscaped segments)
267+ (makeQueries q_params) mempty
268+ where
269+ makeQueries :: [Param] -> String
270+ makeQueries [] = ""
271+ makeQueries xs =
272+ "?" <> intercalate "&" (fmap makeQuery xs)
273+
274+ makeQuery :: Param -> String
275+ makeQuery (ArrayElemParam k v) = escape k <> style <> escape (Text.unpack v)
276+ makeQuery (SingleParam k v) = escape k <> "=" <> escape (Text.unpack v)
277+ makeQuery (FlagParam k) = escape k
278+
279+ style = case addBrackets of
280+ LinkArrayElementBracket -> "[]="
281+ LinkArrayElementPlain -> "="
282+
283+escape :: String -> String
284+escape = escapeURIString isUnreserved
285+
286+-- | Create a valid (by construction) relative URI with query params.
287+--
288+-- This function will only typecheck if `endpoint` is part of the API `api`
289+safeLink
290+ :: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
291+ => Proxy api -- ^ The whole API that this endpoint is a part of
292+ -> Proxy endpoint -- ^ The API endpoint you would like to point to
293+ -> MkLink endpoint Link
294+safeLink = safeLink' id
295+
296+-- | More general 'safeLink'.
297+--
298+safeLink'
299+ :: forall endpoint api a. (IsElem endpoint api, HasLink endpoint)
300+ => (Link -> a)
301+ -> Proxy api -- ^ The whole API that this endpoint is a part of
302+ -> Proxy endpoint -- ^ The API endpoint you would like to point to
303+ -> MkLink endpoint a
304+safeLink' toA _ endpoint = toLink toA endpoint (Link mempty mempty)
305+
306+-- | Create all links in an API.
307+--
308+-- Note that the @api@ type must be restricted to the endpoints that have
309+-- valid links to them.
310+--
311+-- >>> type API = "foo" :> Capture "name" Text :> Get '[JSON] Text :<|> "bar" :> Capture "name" Int :> Get '[JSON] Double
312+-- >>> let fooLink :<|> barLink = allLinks (Proxy :: Proxy API)
313+-- >>> :t fooLink
314+-- fooLink :: Text -> Link
315+-- >>> :t barLink
316+-- barLink :: Int -> Link
317+--
318+-- Note: nested APIs don't work well with this approach
319+--
320+-- >>> :kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link
321+-- MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link :: *
322+-- = Char -> (Int -> Link) :<|> (Double -> Link)
323+allLinks
324+ :: forall api. HasLink api
325+ => Proxy api
326+ -> MkLink api Link
327+allLinks = allLinks' id
328+
329+-- | More general 'allLinks'. See `safeLink'`.
330+allLinks'
331+ :: forall api a. HasLink api
332+ => (Link -> a)
333+ -> Proxy api
334+ -> MkLink api a
335+allLinks' toA api = toLink toA api (Link mempty mempty)
336+
337+-- | Construct a toLink for an endpoint.
338+class HasLink endpoint where
339+ type MkLink endpoint (a :: *)
340+ toLink
341+ :: (Link -> a)
342+ -> Proxy endpoint -- ^ The API endpoint you would like to point to
343+ -> Link
344+ -> MkLink endpoint a
345+
346+-- Naked symbol instance
347+instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
348+ type MkLink (sym :> sub) a = MkLink sub a
349+ toLink toA _ =
350+ toLink toA (Proxy :: Proxy sub) . addSegment (escaped seg)
351+ where
352+ seg = symbolVal (Proxy :: Proxy sym)
353+
354+-- QueryParam instances
355+instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mods))
356+ => HasLink (QueryParam' mods sym v :> sub)
357+ where
358+ type MkLink (QueryParam' mods sym v :> sub) a = If (FoldRequired mods) v (Maybe v) -> MkLink sub a
359+ toLink toA _ l mv =
360+ toLink toA (Proxy :: Proxy sub) $
361+ case sbool :: SBool (FoldRequired mods) of
362+ STrue -> (addQueryParam . SingleParam k . toQueryParam) mv l
363+ SFalse -> maybe id (addQueryParam . SingleParam k . toQueryParam) mv l
364+ where
365+ k :: String
366+ k = symbolVal (Proxy :: Proxy sym)
367+
368+instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
369+ => HasLink (QueryParams sym v :> sub)
370+ where
371+ type MkLink (QueryParams sym v :> sub) a = [v] -> MkLink sub a
372+ toLink toA _ l =
373+ toLink toA (Proxy :: Proxy sub) .
374+ foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l
375+ where
376+ k = symbolVal (Proxy :: Proxy sym)
377+
378+instance (KnownSymbol sym, HasLink sub)
379+ => HasLink (QueryFlag sym :> sub)
380+ where
381+ type MkLink (QueryFlag sym :> sub) a = Bool -> MkLink sub a
382+ toLink toA _ l False =
383+ toLink toA (Proxy :: Proxy sub) l
384+ toLink toA _ l True =
385+ toLink toA (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
386+ where
387+ k = symbolVal (Proxy :: Proxy sym)
388+
389+-- :<|> instance - Generate all links at once
390+instance (HasLink a, HasLink b) => HasLink (a :<|> b) where
391+ type MkLink (a :<|> b) r = MkLink a r :<|> MkLink b r
392+ toLink toA _ l = toLink toA (Proxy :: Proxy a) l :<|> toLink toA (Proxy :: Proxy b) l
393+
394+-- Misc instances
395+instance HasLink sub => HasLink (ReqBody' mods ct a :> sub) where
396+ type MkLink (ReqBody' mods ct a :> sub) r = MkLink sub r
397+ toLink toA _ = toLink toA (Proxy :: Proxy sub)
398+
399+instance (ToHttpApiData v, HasLink sub)
400+ => HasLink (Capture' mods sym v :> sub)
401+ where
402+ type MkLink (Capture' mods sym v :> sub) a = v -> MkLink sub a
403+ toLink toA _ l v =
404+ toLink toA (Proxy :: Proxy sub) $
405+ addSegment (escaped . Text.unpack $ toUrlPiece v) l
406+
407+instance (ToHttpApiData v, HasLink sub)
408+ => HasLink (CaptureAll sym v :> sub)
409+ where
410+ type MkLink (CaptureAll sym v :> sub) a = [v] -> MkLink sub a
411+ toLink toA _ l vs = toLink toA (Proxy :: Proxy sub) $
412+ foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
413+
414+instance HasLink sub => HasLink (Header' mods sym (a :: *) :> sub) where
415+ type MkLink (Header' mods sym a :> sub) r = MkLink sub r
416+ toLink = simpleToLink (Proxy :: Proxy sub)
417+
418+instance HasLink sub => HasLink (Vault :> sub) where
419+ type MkLink (Vault :> sub) a = MkLink sub a
420+ toLink = simpleToLink (Proxy :: Proxy sub)
421+
422+instance HasLink sub => HasLink (Description s :> sub) where
423+ type MkLink (Description s :> sub) a = MkLink sub a
424+ toLink = simpleToLink (Proxy :: Proxy sub)
425+
426+instance HasLink sub => HasLink (Summary s :> sub) where
427+ type MkLink (Summary s :> sub) a = MkLink sub a
428+ toLink = simpleToLink (Proxy :: Proxy sub)
429+
430+instance HasLink sub => HasLink (HttpVersion :> sub) where
431+ type MkLink (HttpVersion:> sub) a = MkLink sub a
432+ toLink = simpleToLink (Proxy :: Proxy sub)
433+
434+instance HasLink sub => HasLink (IsSecure :> sub) where
435+ type MkLink (IsSecure :> sub) a = MkLink sub a
436+ toLink = simpleToLink (Proxy :: Proxy sub)
437+
438+instance HasLink sub => HasLink (WithNamedContext name context sub) where
439+ type MkLink (WithNamedContext name context sub) a = MkLink sub a
440+ toLink toA _ = toLink toA (Proxy :: Proxy sub)
441+
442+instance HasLink sub => HasLink (RemoteHost :> sub) where
443+ type MkLink (RemoteHost :> sub) a = MkLink sub a
444+ toLink = simpleToLink (Proxy :: Proxy sub)
445+
446+instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
447+ type MkLink (BasicAuth realm a :> sub) r = MkLink sub r
448+ toLink = simpleToLink (Proxy :: Proxy sub)
449+
450+instance HasLink EmptyAPI where
451+ type MkLink EmptyAPI a = EmptyAPI
452+ toLink _ _ _ = EmptyAPI
453+
454+-- Verb (terminal) instances
455+instance HasLink (Verb m s ct a) where
456+ type MkLink (Verb m s ct a) r = r
457+ toLink toA _ = toA
458+
459+instance HasLink Raw where
460+ type MkLink Raw a = a
461+ toLink toA _ = toA
462+
463+instance HasLink (Stream m status fr ct a) where
464+ type MkLink (Stream m status fr ct a) r = r
465+ toLink toA _ = toA
466+
467+-- AuthProtext instances
468+instance HasLink sub => HasLink (AuthProtect tag :> sub) where
469+ type MkLink (AuthProtect tag :> sub) a = MkLink sub a
470+ toLink = simpleToLink (Proxy :: Proxy sub)
471+
472+-- | Helper for implemneting 'toLink' for combinators not affecting link
473+-- structure.
474+simpleToLink
475+ :: forall sub a combinator.
476+ (HasLink sub, MkLink sub a ~ MkLink (combinator :> sub) a)
477+ => Proxy sub
478+ -> (Link -> a)
479+ -> Proxy (combinator :> sub)
480+ -> Link
481+ -> MkLink (combinator :> sub) a
482+simpleToLink _ toA _ = toLink toA (Proxy :: Proxy sub)
483+
484+
485+-- $setup
486+-- >>> import Servant.API
487+-- >>> import Data.Text (Text)
servant/src/Servant/Utils/Links.hs+4−485
@@ -1,487 +1,6 @@
1−{-# LANGUAGE ConstraintKinds #-}
2−{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE FlexibleInstances #-}
5−{-# LANGUAGE FunctionalDependencies #-}
6−{-# LANGUAGE PolyKinds #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
8−{-# LANGUAGE TypeFamilies #-}
9−{-# LANGUAGE TypeOperators #-}
10−{-# LANGUAGE UndecidableInstances #-}
11−{-# OPTIONS_HADDOCK not-home #-}
12−
13−-- | Type safe generation of internal links.
14−--
15−-- Given an API with a few endpoints:
16−--
17−-- >>> :set -XDataKinds -XTypeFamilies -XTypeOperators
18−-- >>> import Servant.API
19−-- >>> import Servant.Utils.Links
1+module Servant.Utils.Links
20−-- >>> import Data.Proxy
21−-- >>>
22−-- >>> type Hello = "hello" :> Get '[JSON] Int
23−-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete '[JSON] NoContent
24−-- >>> type API = Hello :<|> Bye
25−-- >>> let api = Proxy :: Proxy API
26−--
27−-- It is possible to generate links that are guaranteed to be within 'API' with
28−-- 'safeLink'. The first argument to 'safeLink' is a type representing the API
29−-- you would like to restrict links to. The second argument is the destination
30−-- endpoint you would like the link to point to, this will need to end with a
31−-- verb like GET or POST. Further arguments may be required depending on the
32−-- type of the endpoint. If everything lines up you will get a 'Link' out the
33−-- other end.
34−--
35−-- You may omit 'QueryParam's and the like should you not want to provide them,
36−-- but types which form part of the URL path like 'Capture' must be included.
37−-- The reason you may want to omit 'QueryParam's is that safeLink is a bit
38−-- magical: if parameters are included that could take input it will return a
39−-- function that accepts that input and generates a link. This is best shown
40−-- with an example. Here, a link is generated with no parameters:
41−--
42−-- >>> let hello = Proxy :: Proxy ("hello" :> Get '[JSON] Int)
43−-- >>> toUrlPiece (safeLink api hello :: Link)
44−-- "hello"
45−--
46−-- If the API has an endpoint with parameters then we can generate links with
47−-- or without those:
48−--
49−-- >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete '[JSON] NoContent)
50−-- >>> toUrlPiece $ safeLink api with (Just "Hubert")
51−-- "bye?name=Hubert"
52−--
53−-- >>> let without = Proxy :: Proxy ("bye" :> Delete '[JSON] NoContent)
54−-- >>> toUrlPiece $ safeLink api without
55−-- "bye"
56−--
57−-- If you would like create a helper for generating links only within that API,
58−-- you can partially apply safeLink if you specify a correct type signature
59−-- like so:
60−--
61−-- >>> :set -XConstraintKinds
62−-- >>> :{
63−-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
64−-- >>> => Proxy endpoint -> MkLink endpoint Link
65−-- >>> apiLink = safeLink api
66−-- >>> :}
67−--
68−-- `safeLink'` allows to make specialise the output:
69−--
70−-- >>> safeLink' toUrlPiece api without
71−-- "bye"
72−--
73−-- >>> :{
74−-- >>> let apiTextLink :: (IsElem endpoint API, HasLink endpoint)
75−-- >>> => Proxy endpoint -> MkLink endpoint Text
76−-- >>> apiTextLink = safeLink' toUrlPiece api
77−-- >>> :}
78−--
79−-- >>> apiTextLink without
80−-- "bye"
81−--
82−-- Attempting to construct a link to an endpoint that does not exist in api
83−-- will result in a type error like this:
84−--
85−-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] NoContent)
86−-- >>> safeLink api bad_link
87−-- ...
88−-- ...Could not deduce...
89−-- ...
90−--
91−-- This error is essentially saying that the type family couldn't find
92−-- bad_link under api after trying the open (but empty) type family
93−-- `IsElem'` as a last resort.
94−module Servant.Utils.Links (
2+ {-# DEPRECATED "Use Servant.Links." #-}
95− module Servant.API.TypeLevel,
3+ ( module Servant.Links )
96−
97− -- * Building and using safe links
98− --
99− -- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
100− safeLink
101− , safeLink'
102− , allLinks
103− , allLinks'
104− , URI(..)
105− -- * Adding custom types
106− , HasLink(..)
107− , Link
108− , linkURI
109− , linkURI'
110− , LinkArrayElementStyle (..)
111− -- ** Link accessors
112− , Param (..)
113− , linkSegments
114− , linkQueryParams
115−) where
116−
117−import Data.List
118−import Data.Proxy
119− (Proxy (..))
120−import Data.Semigroup
121− ((<>))
122−import Data.Singletons.Bool
123− (SBool (..), SBoolI (..))
124−import qualified Data.Text as Text
125−import qualified Data.Text.Encoding as TE
126−import Data.Type.Bool
127− (If)
128−import GHC.TypeLits
129− (KnownSymbol, symbolVal)
130−import Network.URI
131− (URI (..), escapeURIString, isUnreserved)
132−import Prelude ()
133−import Prelude.Compat
134−
135−import Servant.API.Alternative
136− ((:<|>) ((:<|>)))
137−import Servant.API.BasicAuth
138− (BasicAuth)
139−import Servant.API.Capture
140− (Capture', CaptureAll)
141−import Servant.API.Description
142− (Description, Summary)
143−import Servant.API.Empty
144− (EmptyAPI (..))
145−import Servant.API.Experimental.Auth
146− (AuthProtect)
147−import Servant.API.Header
148− (Header')
149−import Servant.API.HttpVersion
150− (HttpVersion)
151−import Servant.API.IsSecure
152− (IsSecure)
153−import Servant.API.Modifiers
154− (FoldRequired)
155−import Servant.API.QueryParam
156− (QueryFlag, QueryParam', QueryParams)
157−import Servant.API.Raw
158− (Raw)
159−import Servant.API.RemoteHost
160− (RemoteHost)
161−import Servant.API.ReqBody
162− (ReqBody')
163−import Servant.API.Stream
164− (Stream)
165−import Servant.API.Sub
166− (type (:>))
167−import Servant.API.TypeLevel
168−import Servant.API.Vault
169− (Vault)
170−import Servant.API.Verbs
171− (Verb)
172−import Servant.API.WithNamedContext
173− (WithNamedContext)
174−import Web.HttpApiData
175−
176−-- | A safe link datatype.
177−-- The only way of constructing a 'Link' is using 'safeLink', which means any
178−-- 'Link' is guaranteed to be part of the mentioned API.
179−data Link = Link
180− { _segments :: [Escaped]
181− , _queryParams :: [Param]
182− } deriving Show
183−
184−newtype Escaped = Escaped String
185−
186−escaped :: String -> Escaped
187−escaped = Escaped . escapeURIString isUnreserved
188−
189−getEscaped :: Escaped -> String
190−getEscaped (Escaped s) = s
191−
192−instance Show Escaped where
193− showsPrec d (Escaped s) = showsPrec d s
194− show (Escaped s) = show s
195−
196−linkSegments :: Link -> [String]
197−linkSegments = map getEscaped . _segments
198−
199−linkQueryParams :: Link -> [Param]
200−linkQueryParams = _queryParams
201−
202−instance ToHttpApiData Link where
203− toHeader = TE.encodeUtf8 . toUrlPiece
204− toUrlPiece l =
205− let uri = linkURI l
206− in Text.pack $ uriPath uri ++ uriQuery uri
207−
208−-- | Query parameter.
209−data Param
210− = SingleParam String Text.Text
211− | ArrayElemParam String Text.Text
212− | FlagParam String
213− deriving Show
214−
215−addSegment :: Escaped -> Link -> Link
216−addSegment seg l = l { _segments = _segments l <> [seg] }
217−
218−addQueryParam :: Param -> Link -> Link
219−addQueryParam qp l =
220− l { _queryParams = _queryParams l <> [qp] }
221−
222−-- | Transform 'Link' into 'URI'.
223−--
224−-- >>> type API = "something" :> Get '[JSON] Int
225−-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
226−-- something
227−--
228−-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
229−-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
230−-- sum?x[]=1&x[]=2&x[]=3
231−--
232−-- >>> type API = "foo/bar" :> Get '[JSON] Int
233−-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
234−-- foo%2Fbar
235−--
236−-- >>> type SomeRoute = "abc" :> Capture "email" String :> Put '[JSON] ()
237−-- >>> let someRoute = Proxy :: Proxy SomeRoute
238−-- >>> safeLink someRoute someRoute "test@example.com"
239−-- Link {_segments = ["abc","test%40example.com"], _queryParams = []}
240−--
241−-- >>> linkURI $ safeLink someRoute someRoute "test@example.com"
242−-- abc/test%40example.com
243−--
244−linkURI :: Link -> URI
245−linkURI = linkURI' LinkArrayElementBracket
246−
247−-- | How to encode array query elements.
248−data LinkArrayElementStyle
249− = LinkArrayElementBracket -- ^ @foo[]=1&foo[]=2@
250− | LinkArrayElementPlain -- ^ @foo=1&foo=2@
251− deriving (Eq, Ord, Show, Enum, Bounded)
252−
253−-- | Configurable 'linkURI'.
254−--
255−-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
256−-- >>> linkURI' LinkArrayElementBracket $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
257−-- sum?x[]=1&x[]=2&x[]=3
258−--
259−-- >>> linkURI' LinkArrayElementPlain $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
260−-- sum?x=1&x=2&x=3
261−--
262−linkURI' :: LinkArrayElementStyle -> Link -> URI
263−linkURI' addBrackets (Link segments q_params) =
264− URI mempty -- No scheme (relative)
265− Nothing -- Or authority (relative)
266− (intercalate "/" $ map getEscaped segments)
267− (makeQueries q_params) mempty
268 where
4 where
269− makeQueries :: [Param] -> String
270− makeQueries [] = ""
271− makeQueries xs =
272− "?" <> intercalate "&" (fmap makeQuery xs)
273−
274− makeQuery :: Param -> String
275− makeQuery (ArrayElemParam k v) = escape k <> style <> escape (Text.unpack v)
276− makeQuery (SingleParam k v) = escape k <> "=" <> escape (Text.unpack v)
277− makeQuery (FlagParam k) = escape k
278−
279− style = case addBrackets of
280− LinkArrayElementBracket -> "[]="
281− LinkArrayElementPlain -> "="
282−
283−escape :: String -> String
284−escape = escapeURIString isUnreserved
285−
286−-- | Create a valid (by construction) relative URI with query params.
287−--
288−-- This function will only typecheck if `endpoint` is part of the API `api`
289−safeLink
290− :: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
291− => Proxy api -- ^ The whole API that this endpoint is a part of
292− -> Proxy endpoint -- ^ The API endpoint you would like to point to
293− -> MkLink endpoint Link
294−safeLink = safeLink' id
295−
296−-- | More general 'safeLink'.
297−--
298−safeLink'
299− :: forall endpoint api a. (IsElem endpoint api, HasLink endpoint)
300− => (Link -> a)
301− -> Proxy api -- ^ The whole API that this endpoint is a part of
302− -> Proxy endpoint -- ^ The API endpoint you would like to point to
303− -> MkLink endpoint a
304−safeLink' toA _ endpoint = toLink toA endpoint (Link mempty mempty)
305−
306−-- | Create all links in an API.
307−--
308−-- Note that the @api@ type must be restricted to the endpoints that have
309−-- valid links to them.
310−--
311−-- >>> type API = "foo" :> Capture "name" Text :> Get '[JSON] Text :<|> "bar" :> Capture "name" Int :> Get '[JSON] Double
312−-- >>> let fooLink :<|> barLink = allLinks (Proxy :: Proxy API)
313−-- >>> :t fooLink
314−-- fooLink :: Text -> Link
315−-- >>> :t barLink
316−-- barLink :: Int -> Link
317−--
318−-- Note: nested APIs don't work well with this approach
319−--
320−-- >>> :kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link
321−-- MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link :: *
322−-- = Char -> (Int -> Link) :<|> (Double -> Link)
323−allLinks
324− :: forall api. HasLink api
325− => Proxy api
326− -> MkLink api Link
327−allLinks = allLinks' id
328−
329−-- | More general 'allLinks'. See `safeLink'`.
330−allLinks'
331− :: forall api a. HasLink api
332− => (Link -> a)
333− -> Proxy api
334− -> MkLink api a
335−allLinks' toA api = toLink toA api (Link mempty mempty)
336−
337−-- | Construct a toLink for an endpoint.
338−class HasLink endpoint where
339− type MkLink endpoint (a :: *)
340− toLink
341− :: (Link -> a)
342− -> Proxy endpoint -- ^ The API endpoint you would like to point to
343− -> Link
344− -> MkLink endpoint a
345−
346−-- Naked symbol instance
347−instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
348− type MkLink (sym :> sub) a = MkLink sub a
349− toLink toA _ =
350− toLink toA (Proxy :: Proxy sub) . addSegment (escaped seg)
351− where
352− seg = symbolVal (Proxy :: Proxy sym)
353−
354−-- QueryParam instances
355−instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mods))
356− => HasLink (QueryParam' mods sym v :> sub)
357− where
358− type MkLink (QueryParam' mods sym v :> sub) a = If (FoldRequired mods) v (Maybe v) -> MkLink sub a
359− toLink toA _ l mv =
360− toLink toA (Proxy :: Proxy sub) $
361− case sbool :: SBool (FoldRequired mods) of
362− STrue -> (addQueryParam . SingleParam k . toQueryParam) mv l
363− SFalse -> maybe id (addQueryParam . SingleParam k . toQueryParam) mv l
364− where
365− k :: String
366− k = symbolVal (Proxy :: Proxy sym)
367−
368−instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
369− => HasLink (QueryParams sym v :> sub)
370− where
371− type MkLink (QueryParams sym v :> sub) a = [v] -> MkLink sub a
372− toLink toA _ l =
373− toLink toA (Proxy :: Proxy sub) .
374− foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l
375− where
376− k = symbolVal (Proxy :: Proxy sym)
377−
378−instance (KnownSymbol sym, HasLink sub)
379− => HasLink (QueryFlag sym :> sub)
380− where
381− type MkLink (QueryFlag sym :> sub) a = Bool -> MkLink sub a
382− toLink toA _ l False =
383− toLink toA (Proxy :: Proxy sub) l
384− toLink toA _ l True =
385− toLink toA (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
386− where
387− k = symbolVal (Proxy :: Proxy sym)
388−
389−-- :<|> instance - Generate all links at once
390−instance (HasLink a, HasLink b) => HasLink (a :<|> b) where
391− type MkLink (a :<|> b) r = MkLink a r :<|> MkLink b r
392− toLink toA _ l = toLink toA (Proxy :: Proxy a) l :<|> toLink toA (Proxy :: Proxy b) l
393−
394−-- Misc instances
395−instance HasLink sub => HasLink (ReqBody' mods ct a :> sub) where
396− type MkLink (ReqBody' mods ct a :> sub) r = MkLink sub r
397− toLink toA _ = toLink toA (Proxy :: Proxy sub)
398−
399−instance (ToHttpApiData v, HasLink sub)
400− => HasLink (Capture' mods sym v :> sub)
401− where
402− type MkLink (Capture' mods sym v :> sub) a = v -> MkLink sub a
403− toLink toA _ l v =
404− toLink toA (Proxy :: Proxy sub) $
405− addSegment (escaped . Text.unpack $ toUrlPiece v) l
406−
407−instance (ToHttpApiData v, HasLink sub)
408− => HasLink (CaptureAll sym v :> sub)
409− where
410− type MkLink (CaptureAll sym v :> sub) a = [v] -> MkLink sub a
411− toLink toA _ l vs = toLink toA (Proxy :: Proxy sub) $
412− foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
413−
414−instance HasLink sub => HasLink (Header' mods sym (a :: *) :> sub) where
415− type MkLink (Header' mods sym a :> sub) r = MkLink sub r
416− toLink = simpleToLink (Proxy :: Proxy sub)
417−
418−instance HasLink sub => HasLink (Vault :> sub) where
419− type MkLink (Vault :> sub) a = MkLink sub a
420− toLink = simpleToLink (Proxy :: Proxy sub)
421−
422−instance HasLink sub => HasLink (Description s :> sub) where
423− type MkLink (Description s :> sub) a = MkLink sub a
424− toLink = simpleToLink (Proxy :: Proxy sub)
425−
426−instance HasLink sub => HasLink (Summary s :> sub) where
427− type MkLink (Summary s :> sub) a = MkLink sub a
428− toLink = simpleToLink (Proxy :: Proxy sub)
429−
430−instance HasLink sub => HasLink (HttpVersion :> sub) where
431− type MkLink (HttpVersion:> sub) a = MkLink sub a
432− toLink = simpleToLink (Proxy :: Proxy sub)
433−
434−instance HasLink sub => HasLink (IsSecure :> sub) where
435− type MkLink (IsSecure :> sub) a = MkLink sub a
436− toLink = simpleToLink (Proxy :: Proxy sub)
437−
438−instance HasLink sub => HasLink (WithNamedContext name context sub) where
439− type MkLink (WithNamedContext name context sub) a = MkLink sub a
440− toLink toA _ = toLink toA (Proxy :: Proxy sub)
441−
442−instance HasLink sub => HasLink (RemoteHost :> sub) where
443− type MkLink (RemoteHost :> sub) a = MkLink sub a
444− toLink = simpleToLink (Proxy :: Proxy sub)
445−
446−instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
447− type MkLink (BasicAuth realm a :> sub) r = MkLink sub r
448− toLink = simpleToLink (Proxy :: Proxy sub)
449−
450−instance HasLink EmptyAPI where
451− type MkLink EmptyAPI a = EmptyAPI
452− toLink _ _ _ = EmptyAPI
453−
454−-- Verb (terminal) instances
455−instance HasLink (Verb m s ct a) where
456− type MkLink (Verb m s ct a) r = r
457− toLink toA _ = toA
458−
459−instance HasLink Raw where
460− type MkLink Raw a = a
461− toLink toA _ = toA
462−
463−instance HasLink (Stream m fr ct a) where
464− type MkLink (Stream m fr ct a) r = r
465− toLink toA _ = toA
466−
467−-- AuthProtext instances
468−instance HasLink sub => HasLink (AuthProtect tag :> sub) where
469− type MkLink (AuthProtect tag :> sub) a = MkLink sub a
470− toLink = simpleToLink (Proxy :: Proxy sub)
471−
472−-- | Helper for implemneting 'toLink' for combinators not affecting link
473−-- structure.
474−simpleToLink
475− :: forall sub a combinator.
476− (HasLink sub, MkLink sub a ~ MkLink (combinator :> sub) a)
477− => Proxy sub
478− -> (Link -> a)
479− -> Proxy (combinator :> sub)
480− -> Link
481− -> MkLink (combinator :> sub) a
482−simpleToLink _ toA _ = toLink toA (Proxy :: Proxy sub)
483−
484
5
485−-- $setup
486−-- >>> import Servant.API
6+import Servant.Links
487−-- >>> import Data.Text (Text)
servant/test/Servant/Utils/LinksSpec.hs → servant/test/Servant/LinksSpec.hsrenamed+3−3
@@ -7,7 +7,7 @@
7#if __GLASGOW_HASKELL__ < 709
7#if __GLASGOW_HASKELL__ < 709
8{-# OPTIONS_GHC -fcontext-stack=41 #-}
8{-# OPTIONS_GHC -fcontext-stack=41 #-}
9#endif
9#endif
10−module Servant.Utils.LinksSpec where
10+module Servant.LinksSpec where
11
11
12import Data.Proxy (Proxy (..))
12import Data.Proxy (Proxy (..))
13import Test.Hspec (Expectation, Spec, describe, it,
13import Test.Hspec (Expectation, Spec, describe, it,
@@ -15,7 +15,7 @@ import Test.Hspec (Expectation, Spec, describe, it,
15import Data.String (fromString)
15import Data.String (fromString)
16
16
17import Servant.API
17import Servant.API
18−import Servant.Utils.Links
18+import Servant.Links
19import Servant.API.Internal.Test.ComprehensiveAPI (comprehensiveAPIWithoutRaw)
19import Servant.API.Internal.Test.ComprehensiveAPI (comprehensiveAPIWithoutRaw)
20
20
21type TestApi =
21type TestApi =
@@ -51,7 +51,7 @@ shouldBeLink link expected =
51 toUrlPiece link `shouldBe` fromString expected
51 toUrlPiece link `shouldBe` fromString expected
52
52
53spec :: Spec
53spec :: Spec
54−spec = describe "Servant.Utils.Links" $ do
54+spec = describe "Servant.Links" $ do
55 it "generates correct links for capture query params" $ do
55 it "generates correct links for capture query params" $ do
56 let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete '[JSON] NoContent)
56 let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete '[JSON] NoContent)
57 apiLink l1 "hi" `shouldBeLink` "hello/hi"
57 apiLink l1 "hi" `shouldBeLink` "hello/hi"
stack.yaml+1−14
@@ -1,5 +1,5 @@
1# Let's try to keep resolver at the first day of the month
1# Let's try to keep resolver at the first day of the month
2−resolver: nightly-2018-03-01
2+resolver: nightly-2018-06-01
3packages:
3packages:
4- servant-client/
4- servant-client/
5- servant-client-core/
5- servant-client-core/
@@ -8,18 +8,5 @@ packages:
8- servant-server/
8- servant-server/
9- servant/
9- servant/
10
10
11−extra-deps:
12−- cabal-doctest-1.0.6
13−- http-api-data-0.3.7.2
14−- http-types-0.12
15−- text-1.2.3.0
16−- aeson-1.3.0.0
17−- exceptions-0.10.0
18−- aeson-compat-0.3.7.1
19−- free-5.0.1
20−- lens-4.16
21−- random-bytestring-0.1.3
22−- pcg-random-0.1.3.5
23−
24# allow-newer: true # ignores all bounds, that's a sledgehammer
11# allow-newer: true # ignores all bounds, that's a sledgehammer
25# - doc/tutorial/
12# - doc/tutorial/