Compare commits
27 Commits
master
...
release-0.
Author | SHA1 | Date |
---|---|---|
Oleg Grenrus | 654b09282b | |
Oleg Grenrus | 0612d2a1d6 | |
Oleg Grenrus | 2f332917d0 | |
Oleg Grenrus | d823307e75 | |
Oleg Grenrus | 12d2a8c177 | |
Oleg Grenrus | d64a662526 | |
Oleg Grenrus | 9a18565027 | |
Oleg Grenrus | 6005c784f3 | |
Oleg Grenrus | d5ed0fe21d | |
Oleg Grenrus | d10520d105 | |
Oleg Grenrus | 84f5f1b06e | |
Oleg Grenrus | 0be69cbf3e | |
Oleg Grenrus | 5fe3b4ea31 | |
Oleg Grenrus | 27768d588f | |
Oleg Grenrus | 706e7192cb | |
Oleg Grenrus | 1107bcf7c9 | |
Oleg Grenrus | 4fc15b9370 | |
Oleg Grenrus | fe0c6ef0cd | |
Oleg Grenrus | 0e2a523da1 | |
Oleg Grenrus | ca2573bf6a | |
Oleg Grenrus | daa5d4a891 | |
Oleg Grenrus | 21bac09010 | |
Alp Mestanogullari | d9cd2c17ba | |
Oleg Grenrus | 2362a682be | |
Oleg Grenrus | 4aa7d44760 | |
Oleg Grenrus | 183ee13731 | |
Oleg Grenrus | d6f3184602 |
74
.travis.yml
74
.travis.yml
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
# runghc make_travis_yml_2.hs '--config=cabal.make-travis-yml' '--output=.travis.yml' 'cabal.project'
|
||||
#
|
||||
# For more information, see https://github.com/hvr/multi-ghc-travis
|
||||
# For more information, see https://github.com/haskell-CI/haskell-ci
|
||||
#
|
||||
language: c
|
||||
sudo: false
|
||||
|
@ -13,7 +13,7 @@ git:
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
- release-0.12
|
||||
- release-0.14
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
@ -33,21 +33,24 @@ before_cache:
|
|||
|
||||
matrix:
|
||||
include:
|
||||
- compiler: "ghc-8.4.3"
|
||||
- compiler: "ghc-8.6.1"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.3], sources: [hvr-ghc]}}
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.1], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-8.4.4"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-8.2.2"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.2], sources: [hvr-ghc]}}
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-8.0.2"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.2], sources: [hvr-ghc]}}
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-7.10.3"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.3], sources: [hvr-ghc]}}
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-7.8.4"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.4], sources: [hvr-ghc]}}
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4], sources: [hvr-ghc]}}
|
||||
|
||||
before_install:
|
||||
- HC=${CC}
|
||||
|
@ -65,17 +68,21 @@ install:
|
|||
- BENCH=${BENCH---enable-benchmarks}
|
||||
- TEST=${TEST---enable-tests}
|
||||
- HADDOCK=${HADDOCK-true}
|
||||
- INSTALLED=${INSTALLED-true}
|
||||
- UNCONSTRAINED=${UNCONSTRAINED-true}
|
||||
- NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}
|
||||
- GHCHEAD=${GHCHEAD-false}
|
||||
- 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/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"
|
||||
- "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
|
||||
- "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, http-media:base' >> cabal.project"
|
||||
- cat cabal.project
|
||||
- "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/generic\" \"doc/cookbook/https\" \"doc/cookbook/structuring-apis\" \"doc/cookbook/using-custom-monad\"\\n' > cabal.project"
|
||||
- "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12, containers installed' >> cabal.project"
|
||||
- "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, servant-js:base,servant-pagination:servant,servant-pagination:servant-server' >> cabal.project"
|
||||
- touch cabal.project.local
|
||||
- "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | grep -vw -- servant | grep -vw -- servant-client | grep -vw -- servant-client-core | grep -vw -- servant-docs | grep -vw -- servant-foreign | grep -vw -- servant-server | grep -vw -- tutorial | grep -vw -- cookbook-basic-auth | grep -vw -- cookbook-db-postgres-pool | grep -vw -- cookbook-db-sqlite-simple | grep -vw -- cookbook-file-upload | grep -vw -- cookbook-generic | grep -vw -- cookbook-https | grep -vw -- cookbook-structuring-apis | grep -vw -- cookbook-using-custom-monad | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
|
||||
- cat cabal.project || true
|
||||
- cat cabal.project.local || true
|
||||
- if [ -f "servant/configure.ac" ]; then
|
||||
(cd "servant" && autoreconf -i);
|
||||
fi
|
||||
|
@ -106,12 +113,15 @@ install:
|
|||
- if [ -f "doc/cookbook/db-sqlite-simple/configure.ac" ]; then
|
||||
(cd "doc/cookbook/db-sqlite-simple" && autoreconf -i);
|
||||
fi
|
||||
- if [ -f "doc/cookbook/file-upload/configure.ac" ]; then
|
||||
(cd "doc/cookbook/file-upload" && autoreconf -i);
|
||||
fi
|
||||
- if [ -f "doc/cookbook/generic/configure.ac" ]; then
|
||||
(cd "doc/cookbook/generic" && autoreconf -i);
|
||||
fi
|
||||
- if [ -f "doc/cookbook/https/configure.ac" ]; then
|
||||
(cd "doc/cookbook/https" && autoreconf -i);
|
||||
fi
|
||||
- if [ -f "doc/cookbook/pagination/configure.ac" ]; then
|
||||
(cd "doc/cookbook/pagination" && autoreconf -i);
|
||||
fi
|
||||
- if [ -f "doc/cookbook/structuring-apis/configure.ac" ]; then
|
||||
(cd "doc/cookbook/structuring-apis" && autoreconf -i);
|
||||
fi
|
||||
|
@ -119,7 +129,7 @@ install:
|
|||
(cd "doc/cookbook/using-custom-monad" && 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/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
|
||||
- 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/generic"/dist "doc/cookbook/https"/dist "doc/cookbook/structuring-apis"/dist "doc/cookbook/using-custom-monad"/dist
|
||||
- DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
|
||||
|
||||
# Here starts the actual work to be performed for the package under test;
|
||||
|
@ -127,32 +137,21 @@ install:
|
|||
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/basic-auth" && cabal sdist)
|
||||
- (cd "doc/cookbook/db-postgres-pool" && cabal sdist)
|
||||
- (cd "doc/cookbook/db-sqlite-simple" && cabal sdist)
|
||||
- (cd "doc/cookbook/https" && cabal sdist)
|
||||
- (cd "doc/cookbook/pagination" && cabal sdist)
|
||||
- (cd "doc/cookbook/structuring-apis" && cabal sdist)
|
||||
- (cd "doc/cookbook/using-custom-monad" && cabal sdist)
|
||||
- cabal new-sdist all
|
||||
- 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/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}/
|
||||
- mv dist-newstyle/sdist/*.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-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"
|
||||
- "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12' >> cabal.project"
|
||||
- "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, http-media:base' >> cabal.project"
|
||||
- cat cabal.project
|
||||
- "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-generic-*/*.cabal cookbook-https-*/*.cabal cookbook-structuring-apis-*/*.cabal cookbook-using-custom-monad-*/*.cabal\\n' > cabal.project"
|
||||
- "echo 'constraints: foundation >=0.0.14,memory <0.14.12 || >0.14.12, containers installed' >> cabal.project"
|
||||
- "echo 'allow-newer: servant-auth-server:http-types,servant-auth-server:servant-server, servant-js:base,servant-pagination:servant,servant-pagination:servant-server' >> cabal.project"
|
||||
- touch cabal.project.local
|
||||
- "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | grep -vw -- servant | grep -vw -- servant-client | grep -vw -- servant-client-core | grep -vw -- servant-docs | grep -vw -- servant-foreign | grep -vw -- servant-server | grep -vw -- tutorial | grep -vw -- cookbook-basic-auth | grep -vw -- cookbook-db-postgres-pool | grep -vw -- cookbook-db-sqlite-simple | grep -vw -- cookbook-file-upload | grep -vw -- cookbook-generic | grep -vw -- cookbook-https | grep -vw -- cookbook-structuring-apis | grep -vw -- cookbook-using-custom-monad | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
|
||||
- cat cabal.project || true
|
||||
- cat cabal.project.local || true
|
||||
- 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} all
|
||||
|
@ -161,7 +160,6 @@ script:
|
|||
|
||||
- echo Haddock... && echo -en 'travis_fold:start:haddock\\r'
|
||||
# haddock
|
||||
- rm -rf ./dist-newstyle
|
||||
- if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
|
||||
|
||||
- echo -en 'travis_fold:end:haddock\\r'
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
folds: all-but-test
|
||||
branches: master release-0.12
|
||||
branches: master release-0.14
|
||||
|
||||
-- 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
|
||||
unconstrained-step: False
|
||||
|
||||
-- Don't run cabal check, as cookbook examples won't pass it
|
||||
cabal-check: False
|
||||
|
|
|
@ -11,12 +11,11 @@ packages: servant/
|
|||
doc/cookbook/basic-auth
|
||||
doc/cookbook/db-postgres-pool
|
||||
doc/cookbook/db-sqlite-simple
|
||||
-- MkLink changed
|
||||
-- doc/cookbook/file-upload
|
||||
doc/cookbook/file-upload
|
||||
doc/cookbook/generic
|
||||
doc/cookbook/https
|
||||
-- servant-auth-* doesn't support GHC-8.4
|
||||
-- doc/cookbook/jwt-and-basic-auth
|
||||
doc/cookbook/pagination
|
||||
-- doc/cookbook/pagination
|
||||
doc/cookbook/structuring-apis
|
||||
doc/cookbook/using-custom-monad
|
||||
|
||||
|
@ -29,10 +28,9 @@ constraints:
|
|||
foundation >=0.0.14,
|
||||
memory <0.14.12 || >0.14.12
|
||||
|
||||
allow-newer:
|
||||
http-media:base
|
||||
constraints: containers installed
|
||||
|
||||
allow-newer:
|
||||
servant-js:servant,
|
||||
servant-js:base,
|
||||
servant-pagination:servant,
|
||||
servant-pagination:servant-server
|
||||
|
|
216
doc/Makefile
216
doc/Makefile
|
@ -1,216 +1,20 @@
|
|||
# Makefile for Sphinx documentation
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
SPHINXPROJ = Servant
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
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
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@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)"
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
.PHONY: help Makefile
|
||||
|
||||
.PHONY: html
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@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."
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
229
doc/conf.py
229
doc/conf.py
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# servant documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Nov 23 13:24:36 2015.
|
||||
# Servant documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Jul 6 11:38:51 2018.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
|
@ -12,20 +12,21 @@
|
|||
# All configuration values have a default; values that are commented out
|
||||
# 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,
|
||||
# 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.
|
||||
#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 ------------------------------------------------
|
||||
|
||||
# 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
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
|
@ -37,17 +38,15 @@ templates_path = ['_templates']
|
|||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
source_suffix = ['.md', '.rst', '.lhs']
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
#
|
||||
source_suffix = ['.rst', '.md', '.lhs']
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'servant'
|
||||
copyright = u'2016, Servant Contributors'
|
||||
project = u'Servant'
|
||||
copyright = u'2018, Servant Contributors'
|
||||
author = u'Servant Contributors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
|
@ -55,9 +54,9 @@ author = u'Servant Contributors'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
# version = 'latest'
|
||||
# version = u'0.14'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# release = 'latest'
|
||||
# release = u'0.14'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# 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.
|
||||
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
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build', 'venv']
|
||||
|
||||
# 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
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
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.
|
||||
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
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_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
|
||||
# documentation.
|
||||
#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
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# 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,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# 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
|
||||
# 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'
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'servantdoc'
|
||||
htmlhelp_basename = 'Servantdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'servant.tex', u'servant Documentation',
|
||||
(master_doc, 'Servant.tex', u'Servant Documentation',
|
||||
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 ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'servant', u'servant Documentation',
|
||||
(master_doc, 'servant', u'Servant Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
|
@ -271,24 +159,15 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'servant', u'servant Documentation',
|
||||
author, 'servant', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(master_doc, 'Servant', u'Servant Documentation',
|
||||
author, 'Servant', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#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
|
||||
# -- Markdown -------------------------------------------------------------
|
||||
|
||||
source_parsers = {
|
||||
'.md': CommonMarkParser,
|
||||
'.lhs': CommonMarkParser,
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,13 @@ author: Servant Contributors
|
|||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-basic-auth
|
||||
main-is: BasicAuth.lhs
|
||||
|
|
|
@ -8,7 +8,13 @@ author: Servant Contributors
|
|||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-db-postgres-pool
|
||||
main-is: PostgresPool.lhs
|
||||
|
|
|
@ -8,7 +8,13 @@ author: Servant Contributors
|
|||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-db-sqlite-simple
|
||||
main-is: DBConnection.lhs
|
||||
|
|
|
@ -8,7 +8,13 @@ author: Servant Contributors
|
|||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-file-upload
|
||||
main-is: FileUpload.lhs
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
# Using generics
|
||||
|
||||
```haskell
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
module Main (main, api, getLink, routesLinks, cliGet) where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
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 recommented 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 conviently. 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 let 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
|
||||
_ -> putStrLn "To run, pass 'run' argument: cabal new-run cookbook-generic run"
|
||||
```
|
|
@ -0,0 +1,31 @@
|
|||
name: cookbook-generic
|
||||
version: 0.1
|
||||
synopsis: Using custom monad to pass a state between handlers
|
||||
homepage: http://haskell-servant.readthedocs.org/
|
||||
license: BSD3
|
||||
license-file: ../../../servant/LICENSE
|
||||
author: Servant Contributors
|
||||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
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
|
|
@ -8,7 +8,13 @@ author: Servant Contributors
|
|||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-https
|
||||
main-is: Https.lhs
|
||||
|
|
|
@ -18,6 +18,7 @@ you name it!
|
|||
:maxdepth: 1
|
||||
|
||||
structuring-apis/StructuringApis.lhs
|
||||
generic/Generic.lhs
|
||||
https/Https.lhs
|
||||
db-sqlite-simple/DBConnection.lhs
|
||||
db-postgres-pool/PostgresPool.lhs
|
||||
|
|
|
@ -11,7 +11,13 @@ maintainer: haskell-servant-maintainers@googlegroups.com
|
|||
category: Servant
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-jwt-and-basic-auth
|
||||
if !impl(ghc >= 7.10)
|
||||
|
|
|
@ -11,7 +11,13 @@ cabal-version: >=1.10
|
|||
extra-source-files:
|
||||
Pagination.lhs
|
||||
dummy/Pagination.lhs
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-pagination
|
||||
main-is: Pagination.lhs
|
||||
|
@ -21,7 +27,7 @@ executable cookbook-pagination
|
|||
|
||||
if impl(ghc >= 8.0)
|
||||
hs-source-dirs: .
|
||||
build-depends: base >= 4.8 && <4.12
|
||||
build-depends: base >= 4.8
|
||||
, aeson
|
||||
, servant
|
||||
, servant-server
|
||||
|
|
|
@ -8,7 +8,13 @@ author: Servant Contributors
|
|||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-structuring-apis
|
||||
main-is: StructuringApis.lhs
|
||||
|
|
|
@ -8,7 +8,13 @@ author: Servant Contributors
|
|||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
|
||||
tested-with:
|
||||
GHC==7.8.4
|
||||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
executable cookbook-using-custom-monad
|
||||
main-is: UsingCustomMonad.lhs
|
||||
|
|
|
@ -3,22 +3,24 @@ servant – A Type-Level Web DSL
|
|||
|
||||
.. 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
|
||||
then using those API specifications to:
|
||||
**servant** is a set of Haskell libraries for writing *type-safe* web
|
||||
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),
|
||||
- obtain client functions (in haskell),
|
||||
- generate client functions for other programming languages,
|
||||
- generate documentation for your web applications
|
||||
- and more...
|
||||
This is achieved by taking as input a description of the web API
|
||||
as a Haskell type. Servant is then able to check that your server-side request
|
||||
handlers indeed implement your web API faithfully, or to automatically derive
|
||||
Haskell functions that can hit a web application that implements this API,
|
||||
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::
|
||||
:maxdepth: 2
|
||||
|
||||
introduction.rst
|
||||
tutorial/index.rst
|
||||
cookbook/index.rst
|
||||
examples.md
|
||||
links.rst
|
||||
principles.rst
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=Servant
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
|
@ -1,5 +1,5 @@
|
|||
Introduction
|
||||
------------
|
||||
Principles
|
||||
----------
|
||||
|
||||
**servant** has the following guiding principles:
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
alabaster==0.7.11
|
||||
argh==0.26.2
|
||||
Babel==2.6.0
|
||||
backports-abc==0.5
|
||||
backports.ssl-match-hostname==3.5.0.1
|
||||
certifi==2018.4.16
|
||||
CommonMark==0.7.5
|
||||
docutils==0.14
|
||||
Jinja2==2.10
|
||||
livereload==2.5.2
|
||||
MarkupSafe==1.0
|
||||
pathtools==0.1.2
|
||||
Pygments==2.2.0
|
||||
pytz==2018.5
|
||||
PyYAML==3.13
|
||||
recommonmark==0.4.0
|
||||
singledispatch==3.4.0.3
|
||||
six==1.11.0
|
||||
snowballstemmer==1.2.1
|
||||
Sphinx==1.6.5
|
||||
sphinx-autobuild==0.7.1
|
||||
sphinx_rtd_theme>=0.4.0
|
||||
tornado==5.0.2
|
||||
watchdog==0.8.3
|
||||
wheel==0.31.1
|
|
@ -1,25 +1,3 @@
|
|||
alabaster==0.7.7
|
||||
argh==0.26.1
|
||||
Babel==2.2.0
|
||||
backports-abc==0.4
|
||||
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
|
||||
Sphinx==1.7.5
|
||||
sphinx_rtd_theme>=0.4.0
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(name='servant-rtd',
|
||||
version='0.0.1',
|
||||
description='Documentation for the haskell-servant platform',
|
||||
url='https://github.com/haskell-servant/servant',
|
||||
author='Servant Contributors',
|
||||
author_email='haskell-servant-maintainers@googlegroups.com',
|
||||
license='BSD3',
|
||||
zip_safe=False)
|
||||
|
|
@ -477,7 +477,7 @@ data AngularOptions = AngularOptions
|
|||
}
|
||||
```
|
||||
|
||||
# Custom function name builder
|
||||
## Custom function name builder
|
||||
|
||||
Servant comes with three name builders included:
|
||||
|
||||
|
@ -518,4 +518,3 @@ var get_books = function(q, onSuccess, onError)
|
|||
}
|
||||
|
||||
```
|
||||
|
||||
|
|
|
@ -3,9 +3,28 @@ 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.
|
||||
|
||||
(Any comments, issues or feedback about the tutorial can be submitted
|
||||
to `servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.)
|
||||
Any comments, issues or feedback about the tutorial can be submitted
|
||||
to `servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.
|
||||
|
||||
In fact, the whole tutorial is a `cabal <https://cabal.readthedocs.io/en/latest/>`_
|
||||
project and can be built and played with 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
|
||||
|
||||
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 <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.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
|
|
@ -17,7 +17,8 @@ tested-with:
|
|||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.3
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
extra-source-files:
|
||||
static/index.html
|
||||
static/ui.js
|
||||
|
@ -35,7 +36,7 @@ library
|
|||
-- Packages `servant` depends on.
|
||||
-- We don't need to specify bounds here as this package is never released.
|
||||
build-depends:
|
||||
base >= 4.7 && <4.12
|
||||
base >= 4.7 && <4.13
|
||||
, aeson
|
||||
, aeson-compat
|
||||
, attoparsec
|
||||
|
@ -75,8 +76,8 @@ library
|
|||
, time >= 1.4.2 && < 1.9
|
||||
|
||||
-- For legacy tools, we need to specify build-depends too
|
||||
build-depends: markdown-unlit >= 0.4.1 && <0.5
|
||||
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4.1 && <0.5
|
||||
build-depends: markdown-unlit >= 0.5.0 && <0.6
|
||||
build-tool-depends: markdown-unlit:markdown-unlit >= 0.5.0 && <0.6
|
||||
|
||||
test-suite spec
|
||||
type: exitcode-stdio-1.0
|
||||
|
|
|
@ -1,13 +1,61 @@
|
|||
[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-client-core/CHANGELOG.md)
|
||||
[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
|
||||
|
||||
0.14.1
|
||||
------
|
||||
|
||||
- Merge in `servant-generic` (by [Patrick Chilton](https://github.com/chpatrick))
|
||||
into `servant` (`Servant.API.Generic`),
|
||||
`servant-client-code` (`Servant.Client.Generic`)
|
||||
and `servant-server` (`Servant.Server.Generic`).
|
||||
|
||||
0.14
|
||||
----
|
||||
|
||||
- Add a `hoistClientMonad` method to the `HasClient` typeclass, for
|
||||
changing the monad in which client functions run.
|
||||
- `Stream` takes a status code argument
|
||||
|
||||
```diff
|
||||
-Stream method framing ctype a
|
||||
+Stream method status framing ctype a
|
||||
```
|
||||
|
||||
([#966](https://github.com/haskell-servant/servant/pull/966)
|
||||
[#972](https://github.com/haskell-servant/servant/pull/972))
|
||||
|
||||
- `ToStreamGenerator` definition changed, so it's possible to write an instance
|
||||
for conduits.
|
||||
|
||||
```diff
|
||||
-class ToStreamGenerator f a where
|
||||
- toStreamGenerator :: f a -> StreamGenerator a
|
||||
+class ToStreamGenerator a b | a -> b where
|
||||
+ toStreamGenerator :: a -> StreamGenerator b
|
||||
```
|
||||
|
||||
([#959](https://github.com/haskell-servant/servant/pull/959))
|
||||
|
||||
- Added `NoFraming` streaming strategy
|
||||
([#959](https://github.com/haskell-servant/servant/pull/959))
|
||||
|
||||
- *servant-client-core* Free `Client` implementation.
|
||||
Useful for testing `HasClient` instances.
|
||||
([#920](https://github.com/haskell-servant/servant/pull/920))
|
||||
|
||||
- *servant-client-core* Add `hoistClient` to `HasClient`.
|
||||
Just like `hoistServer` allows us to change the monad in which request handlers
|
||||
of a web application live in, we also have `hoistClient` for changing the monad
|
||||
in which *client functions* live.
|
||||
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).
|
||||
([#936](https://github.com/haskell-servant/servant/pull/936))
|
||||
|
||||
iF you have own combinators, you'll need to define a new method of
|
||||
`HasClient` class, for example:
|
||||
|
||||
```haskell
|
||||
type Client m (MyCombinator :> api) = MyValue :> Client m api
|
||||
hoistClientMonad pm _ nt cl = hoistClientMonad pm (Proxy :: Proxy api) nt . cl
|
||||
```
|
||||
|
||||
0.13.0.1
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
name: servant-client-core
|
||||
version: 0.14
|
||||
version: 0.14.1
|
||||
x-revision: 2
|
||||
|
||||
synopsis: Core functionality and class for client function generation for servant APIs
|
||||
category: Web, Servant
|
||||
description:
|
||||
This library provides backend-agnostic generation of client functions. For
|
||||
more information, see the README.
|
||||
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Servant Contributors
|
||||
|
@ -12,7 +16,6 @@ homepage: http://haskell-servant.readthedocs.org/
|
|||
bug-reports: http://github.com/haskell-servant/servant/issues
|
||||
cabal-version: >=1.10
|
||||
copyright: 2014-2016 Zalora South East Asia Pte Ltd, 2016-2017 Servant Contributors
|
||||
category: Web
|
||||
build-type: Simple
|
||||
extra-source-files:
|
||||
include/*.h
|
||||
|
@ -23,7 +26,8 @@ tested-with:
|
|||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.3
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
source-repository head
|
||||
type: git
|
||||
|
@ -33,6 +37,7 @@ library
|
|||
exposed-modules:
|
||||
Servant.Client.Core
|
||||
Servant.Client.Free
|
||||
Servant.Client.Generic
|
||||
Servant.Client.Core.Reexport
|
||||
Servant.Client.Core.Internal.Auth
|
||||
Servant.Client.Core.Internal.BaseUrl
|
||||
|
@ -48,33 +53,32 @@ library
|
|||
--
|
||||
-- note: mtl lower bound is so low because of GHC-7.8
|
||||
build-depends:
|
||||
base >= 4.7 && < 4.12
|
||||
base >= 4.7 && < 4.13
|
||||
, bytestring >= 0.10.4.0 && < 0.11
|
||||
, containers >= 0.5.5.1 && < 0.6
|
||||
, mtl >= 2.1 && < 2.3
|
||||
, containers >= 0.5.5.1 && < 0.7
|
||||
, text >= 1.2.3.0 && < 1.3
|
||||
|
||||
if !impl(ghc >= 8.0)
|
||||
build-depends:
|
||||
semigroups >=0.18.3 && <0.19
|
||||
semigroups >=0.18.4 && <0.19
|
||||
|
||||
-- Servant dependencies
|
||||
build-depends:
|
||||
servant == 0.14.*
|
||||
servant >= 0.14.1 && <0.15
|
||||
|
||||
-- Other dependencies: Lower bound around what is in the latest Stackage LTS.
|
||||
-- Here can be exceptions if we really need features from the newer versions.
|
||||
build-depends:
|
||||
base-compat >= 0.9.3 && < 0.11
|
||||
base-compat >= 0.10.1 && < 0.11
|
||||
, base64-bytestring >= 1.0.0.1 && < 1.1
|
||||
, exceptions >= 0.8.3 && < 0.11
|
||||
, free >= 5.0.1 && < 5.1
|
||||
, generics-sop >= 0.3.1.0 && < 0.4
|
||||
, http-api-data >= 0.3.7.1 && < 0.4
|
||||
, http-media >= 0.7.1.1 && < 0.8
|
||||
, http-types >= 0.12 && < 0.13
|
||||
, exceptions >= 0.10.0 && < 0.11
|
||||
, free >= 5.0.2 && < 5.2
|
||||
, generics-sop >= 0.3.2.0 && < 0.5
|
||||
, http-api-data >= 0.3.8.1 && < 0.4
|
||||
, http-media >= 0.7.1.2 && < 0.8
|
||||
, http-types >= 0.12.1 && < 0.13
|
||||
, network-uri >= 2.6.1.0 && < 2.7
|
||||
, safe >= 0.3.15 && < 0.4
|
||||
, safe >= 0.3.17 && < 0.4
|
||||
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
|
@ -99,8 +103,8 @@ test-suite spec
|
|||
-- Additonal dependencies
|
||||
build-depends:
|
||||
deepseq >= 1.3.0.2 && <1.5
|
||||
, hspec >= 2.4.4 && <2.6
|
||||
, QuickCheck >= 2.10.1 && < 2.12
|
||||
, hspec >= 2.4.1 && <2.6
|
||||
, QuickCheck >= 2.11.3 && < 2.13
|
||||
|
||||
build-tool-depends:
|
||||
hspec-discover:hspec-discover >= 2.4.4 && <2.6
|
||||
hspec-discover:hspec-discover >= 2.5.1 && <2.6
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
{-# LANGUAGE ConstraintKinds #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE KindSignatures #-}
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
module Servant.Client.Generic (
|
||||
AsClientT,
|
||||
genericClient,
|
||||
genericClientHoist,
|
||||
) where
|
||||
|
||||
import Data.Proxy
|
||||
(Proxy (..))
|
||||
|
||||
import Servant.API.Generic
|
||||
import Servant.Client.Core
|
||||
|
||||
-- | A type that specifies that an API reocrd contains a client implementation.
|
||||
data AsClientT (m :: * -> *)
|
||||
instance GenericMode (AsClientT m) where
|
||||
type AsClientT m :- api = Client m api
|
||||
|
||||
-- | Generate a record of client functions.
|
||||
genericClient
|
||||
:: forall routes m.
|
||||
( HasClient m (ToServantApi routes)
|
||||
, GenericServant routes (AsClientT m)
|
||||
, Client m (ToServantApi routes) ~ ToServant routes (AsClientT m)
|
||||
)
|
||||
=> routes (AsClientT m)
|
||||
genericClient
|
||||
= fromServant
|
||||
$ clientIn (Proxy :: Proxy (ToServantApi routes)) (Proxy :: Proxy m)
|
||||
|
||||
-- | 'genericClient' but with 'hoistClientMonad' in between.
|
||||
genericClientHoist
|
||||
:: forall routes m n.
|
||||
( HasClient m (ToServantApi routes)
|
||||
, GenericServant routes (AsClientT n)
|
||||
, Client n (ToServantApi routes) ~ ToServant routes (AsClientT n)
|
||||
)
|
||||
=> (forall x. m x -> n x) -- ^ natural transformation
|
||||
-> routes (AsClientT n)
|
||||
genericClientHoist nt
|
||||
= fromServant
|
||||
$ hoistClientMonad m api nt
|
||||
$ clientIn api m
|
||||
where
|
||||
m = Proxy :: Proxy m
|
||||
api = Proxy :: Proxy (ToServantApi routes)
|
|
@ -31,10 +31,10 @@ library
|
|||
Servant.Client.Ghcjs
|
||||
Servant.Client.Internal.XhrClient
|
||||
build-depends:
|
||||
base >= 4.7 && < 4.12
|
||||
base >= 4.7 && < 4.13
|
||||
, bytestring >= 0.10 && < 0.11
|
||||
, case-insensitive >= 1.2.0.0 && < 1.3.0.0
|
||||
, containers >= 0.5 && < 0.6
|
||||
, containers >= 0.5 && < 0.7
|
||||
, exceptions >= 0.8 && < 0.11
|
||||
, ghcjs-base >= 0.2.0.0 && < 0.3.0.0
|
||||
, ghcjs-prim >= 0.1.0.0 && < 0.2.0.0
|
||||
|
@ -42,7 +42,7 @@ library
|
|||
, http-types >= 0.12 && < 0.13
|
||||
, monad-control >= 1.0.0.4 && < 1.1
|
||||
, mtl >= 2.1 && < 2.3
|
||||
, semigroupoids >= 4.3 && < 5.3
|
||||
, semigroupoids >= 4.3 && < 5.4
|
||||
, servant-client-core == 0.14.*
|
||||
, string-conversions >= 0.3 && < 0.5
|
||||
, transformers >= 0.3 && < 0.6
|
||||
|
|
|
@ -4,10 +4,45 @@
|
|||
0.14
|
||||
----
|
||||
|
||||
- Add `hoistClient` for changing the monad in which
|
||||
client functions run.
|
||||
- `Stream` takes a status code argument
|
||||
|
||||
```diff
|
||||
-Stream method framing ctype a
|
||||
+Stream method status framing ctype a
|
||||
```
|
||||
|
||||
([#966](https://github.com/haskell-servant/servant/pull/966)
|
||||
[#972](https://github.com/haskell-servant/servant/pull/972))
|
||||
|
||||
- `ToStreamGenerator` definition changed, so it's possible to write an instance
|
||||
for conduits.
|
||||
|
||||
```diff
|
||||
-class ToStreamGenerator f a where
|
||||
- toStreamGenerator :: f a -> StreamGenerator a
|
||||
+class ToStreamGenerator a b | a -> b where
|
||||
+ toStreamGenerator :: a -> StreamGenerator b
|
||||
```
|
||||
|
||||
([#959](https://github.com/haskell-servant/servant/pull/959))
|
||||
|
||||
- Added `NoFraming` streaming strategy
|
||||
([#959](https://github.com/haskell-servant/servant/pull/959))
|
||||
|
||||
- *servant-client-core* Add `hoistClient` to `HasClient`.
|
||||
Just like `hoistServer` allows us to change the monad in which request handlers
|
||||
of a web application live in, we also have `hoistClient` for changing the monad
|
||||
in which *client functions* live.
|
||||
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).
|
||||
([#936](https://github.com/haskell-servant/servant/pull/936))
|
||||
|
||||
- *servant-client* Add more constructors to `RequestBody`, including
|
||||
`RequestBodyStream`.
|
||||
*Note:* we are looking for http-library agnostic API,
|
||||
so the might change again soon.
|
||||
Tell us which constructors are useful for you!
|
||||
([#913](https://github.com/haskell-servant/servant/pull/913))
|
||||
|
||||
0.13.0.1
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
name: servant-client
|
||||
version: 0.14
|
||||
x-revision: 3
|
||||
|
||||
synopsis: automatical derivation of querying functions for servant webservices
|
||||
category: Servant, Web
|
||||
description:
|
||||
This library lets you derive automatically Haskell functions that
|
||||
let you query each endpoint of a <http://hackage.haskell.org/package/servant servant> webservice.
|
||||
|
@ -8,12 +11,12 @@ description:
|
|||
See <http://haskell-servant.readthedocs.org/en/stable/tutorial/Client.html the client section of the tutorial>.
|
||||
.
|
||||
<https://github.com/haskell-servant/servant/blob/master/servant-client/CHANGELOG.md CHANGELOG>
|
||||
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Servant Contributors
|
||||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
copyright: 2014-2016 Zalora South East Asia Pte Ltd, 2016-2017 Servant Contributors
|
||||
category: Servant, Web
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with:
|
||||
|
@ -21,7 +24,9 @@ tested-with:
|
|||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.3
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
homepage: http://haskell-servant.readthedocs.org/
|
||||
Bug-reports: http://github.com/haskell-servant/servant/issues
|
||||
extra-source-files:
|
||||
|
@ -42,16 +47,16 @@ library
|
|||
--
|
||||
-- note: mtl lower bound is so low because of GHC-7.8
|
||||
build-depends:
|
||||
base >= 4.7 && < 4.12
|
||||
base >= 4.7 && < 4.13
|
||||
, bytestring >= 0.10.4.0 && < 0.11
|
||||
, containers >= 0.5.5.1 && < 0.6
|
||||
, containers >= 0.5.5.1 && < 0.7
|
||||
, mtl >= 2.1 && < 2.3
|
||||
, text >= 1.2.3.0 && < 1.3
|
||||
, time >= 1.4.2 && < 1.9
|
||||
, transformers >= 0.3.0.0 && < 0.6
|
||||
|
||||
if !impl(ghc >= 8.0)
|
||||
build-depends: semigroups >=0.18.3 && <0.19
|
||||
build-depends: semigroups >=0.18.4 && <0.19
|
||||
|
||||
-- Servant dependencies
|
||||
build-depends:
|
||||
|
@ -60,19 +65,16 @@ library
|
|||
-- Other dependencies: Lower bound around what is in the latest Stackage LTS.
|
||||
-- Here can be exceptions if we really need features from the newer versions.
|
||||
build-depends:
|
||||
aeson >= 1.2.3.0 && < 1.5
|
||||
, base-compat >= 0.9.3 && < 0.11
|
||||
, attoparsec >= 0.13.2.0 && < 0.14
|
||||
, http-client >= 0.5.7.1 && < 0.6
|
||||
, http-client-tls >= 0.3.5.1 && < 0.4
|
||||
, http-media >= 0.7.1.1 && < 0.8
|
||||
, http-types >= 0.12 && < 0.13
|
||||
, exceptions >= 0.8.3 && < 0.11
|
||||
, monad-control >= 1.0.0.4 && < 1.1
|
||||
, semigroupoids >= 5.2.1 && < 5.3
|
||||
, stm >= 2.4.4.1 && < 2.5
|
||||
, transformers-base >= 0.4.4 && < 0.5
|
||||
, transformers-compat >= 0.5.1 && < 0.7
|
||||
base-compat >= 0.10.1 && < 0.11
|
||||
, http-client >= 0.5.12 && < 0.6
|
||||
, http-media >= 0.7.1.2 && < 0.8
|
||||
, http-types >= 0.12.1 && < 0.13
|
||||
, exceptions >= 0.10.0 && < 0.11
|
||||
, monad-control >= 1.0.2.3 && < 1.1
|
||||
, semigroupoids >= 5.2.2 && < 5.4
|
||||
, stm >= 2.4.5.0 && < 2.6
|
||||
, transformers-base >= 0.4.5.2 && < 0.5
|
||||
, transformers-compat >= 0.6.2 && < 0.7
|
||||
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
|
@ -97,10 +99,8 @@ test-suite spec
|
|||
, aeson
|
||||
, base-compat
|
||||
, bytestring
|
||||
, containers
|
||||
, http-api-data
|
||||
, http-client
|
||||
, http-media
|
||||
, http-types
|
||||
, mtl
|
||||
, servant-client
|
||||
|
@ -117,18 +117,16 @@ test-suite spec
|
|||
|
||||
-- Additonal dependencies
|
||||
build-depends:
|
||||
deepseq >= 1.3.0.2 && < 1.5
|
||||
, generics-sop >= 0.3.1.0 && < 0.4
|
||||
, hspec >= 2.4.4 && < 2.6
|
||||
generics-sop >= 0.3.2.0 && < 0.5
|
||||
, hspec >= 2.5.1 && < 2.6
|
||||
, HUnit >= 1.6 && < 1.7
|
||||
, random-bytestring >= 0.1 && < 0.2
|
||||
, network >= 2.6.3.2 && < 2.8
|
||||
, QuickCheck >= 2.10.1 && < 2.12
|
||||
, servant == 0.13.*
|
||||
, servant-server == 0.13.*
|
||||
, network >= 2.6.3.2 && < 2.9
|
||||
, QuickCheck >= 2.10.1 && < 2.13
|
||||
, servant == 0.14.*
|
||||
, servant-server == 0.14.*
|
||||
|
||||
build-tool-depends:
|
||||
hspec-discover:hspec-discover >= 2.4.4 && < 2.6
|
||||
hspec-discover:hspec-discover >= 2.5.1 && < 2.6
|
||||
|
||||
test-suite readme
|
||||
type: exitcode-stdio-1.0
|
||||
|
@ -136,3 +134,4 @@ test-suite readme
|
|||
build-depends: base, servant, http-client, text, servant-client, markdown-unlit
|
||||
build-tool-depends: markdown-unlit:markdown-unlit
|
||||
ghc-options: -pgmL markdown-unlit
|
||||
default-language: Haskell2010
|
||||
|
|
|
@ -130,6 +130,7 @@ streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
|
|||
jrb = Just (Right bob)
|
||||
runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
||||
|
||||
{-
|
||||
it "streams in constant memory" $ \(_, baseUrl) -> do
|
||||
Right (ResultStream res) <- runClient getGetALot baseUrl
|
||||
let consumeNChunks n = replicateM_ n (res void)
|
||||
|
@ -140,6 +141,7 @@ streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
|
|||
memUsed <- currentBytesUsed <$> getGCStats
|
||||
#endif
|
||||
memUsed `shouldSatisfy` (< megabytes 22)
|
||||
-}
|
||||
|
||||
megabytes :: Num a => a -> a
|
||||
megabytes n = n * (1000 ^ (2 :: Int))
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
name: servant-docs
|
||||
version: 0.11.2
|
||||
x-revision: 4
|
||||
x-revision: 6
|
||||
|
||||
synopsis: generate API docs for your servant webservice
|
||||
category: Servant, Web
|
||||
description:
|
||||
Library for generating API docs from a servant API definition.
|
||||
.
|
||||
Runnable example <https://github.com/haskell-servant/servant/blob/master/servant-docs/example/greet.hs here>.
|
||||
.
|
||||
<https://github.com/haskell-servant/servant/blob/master/servant-docs/CHANGELOG.md CHANGELOG>
|
||||
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Servant Contributors
|
||||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
copyright: 2014-2016 Zalora South East Asia Pte Ltd, Servant Contributors
|
||||
category: Servant, Web
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
tested-with:
|
||||
|
@ -21,7 +23,9 @@ tested-with:
|
|||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.3
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
homepage: http://haskell-servant.readthedocs.org/
|
||||
Bug-reports: http://github.com/haskell-servant/servant/issues
|
||||
extra-source-files:
|
||||
|
@ -43,7 +47,7 @@ library
|
|||
--
|
||||
-- note: mtl lower bound is so low because of GHC-7.8
|
||||
build-depends:
|
||||
base >= 4.7 && < 4.12
|
||||
base >= 4.7 && < 4.13
|
||||
, bytestring >= 0.10.4.0 && < 0.11
|
||||
, text >= 1.2.3.0 && < 1.3
|
||||
|
||||
|
@ -64,9 +68,9 @@ library
|
|||
, case-insensitive >= 1.2.0.10 && < 1.3
|
||||
, control-monad-omega >= 0.3.1 && < 0.4
|
||||
, hashable >= 1.2.6.1 && < 1.3
|
||||
, http-media >= 0.7.1.1 && < 0.8
|
||||
, http-media >= 0.7.0 && < 0.8
|
||||
, http-types >= 0.12 && < 0.13
|
||||
, lens >= 4.15.4 && < 4.17
|
||||
, lens >= 4.15.4 && < 4.18
|
||||
, string-conversions >= 0.4.0.1 && < 0.5
|
||||
, unordered-containers >= 0.2.8.0 && < 0.3
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
name: servant-foreign
|
||||
version: 0.11.1
|
||||
x-revision: 4
|
||||
|
||||
synopsis: Helpers for generating clients for servant APIs in any programming language
|
||||
category: Servant, Web
|
||||
description:
|
||||
Helper types and functions for generating client functions for servant APIs in any programming language
|
||||
.
|
||||
|
@ -9,12 +12,12 @@ description:
|
|||
See the servant-js package for an example
|
||||
.
|
||||
<https://github.com/haskell-servant/servant/blob/master/servant-foreign/CHANGELOG.md CHANGELOG>
|
||||
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Servant Contributors
|
||||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
copyright: 2015-2016 Servant Contributors
|
||||
category: Servant, Web
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
extra-source-files:
|
||||
|
@ -27,7 +30,8 @@ tested-with:
|
|||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.3
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
source-repository head
|
||||
type: git
|
||||
|
@ -43,7 +47,7 @@ library
|
|||
--
|
||||
-- note: mtl lower bound is so low because of GHC-7.8
|
||||
build-depends:
|
||||
base >= 4.7 && <4.12
|
||||
base >= 4.7 && <4.13
|
||||
, text >= 1.2.3.0 && < 1.3
|
||||
|
||||
if !impl(ghc >= 8.0)
|
||||
|
@ -58,7 +62,7 @@ library
|
|||
-- Here can be exceptions if we really need features from the newer versions.
|
||||
build-depends:
|
||||
base-compat >= 0.9.3 && <0.11
|
||||
, lens >= 4.15.4 && <4.17
|
||||
, lens >= 4.15.4 && <4.18
|
||||
, http-types >= 0.12 && < 0.13
|
||||
|
||||
hs-source-dirs: src
|
||||
|
|
|
@ -1,6 +1,62 @@
|
|||
[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md)
|
||||
[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
|
||||
|
||||
0.14.1
|
||||
------
|
||||
|
||||
- Merge in `servant-generic` (by [Patrick Chilton](https://github.com/chpatrick))
|
||||
into `servant` (`Servant.API.Generic`),
|
||||
`servant-client-code` (`Servant.Client.Generic`)
|
||||
and `servant-server` (`Servant.Server.Generic`).
|
||||
|
||||
- *servant-server* Deprecate `Servant.Utils.StaticUtils`, use `Servant.Server.StaticUtils`.
|
||||
|
||||
0.14
|
||||
----
|
||||
|
||||
- `Stream` takes a status code argument
|
||||
|
||||
```diff
|
||||
-Stream method framing ctype a
|
||||
+Stream method status framing ctype a
|
||||
```
|
||||
|
||||
([#966](https://github.com/haskell-servant/servant/pull/966)
|
||||
[#972](https://github.com/haskell-servant/servant/pull/972))
|
||||
|
||||
- `ToStreamGenerator` definition changed, so it's possible to write an instance
|
||||
for conduits.
|
||||
|
||||
```diff
|
||||
-class ToStreamGenerator f a where
|
||||
- toStreamGenerator :: f a -> StreamGenerator a
|
||||
+class ToStreamGenerator a b | a -> b where
|
||||
+ toStreamGenerator :: a -> StreamGenerator b
|
||||
```
|
||||
|
||||
([#959](https://github.com/haskell-servant/servant/pull/959))
|
||||
|
||||
- Added `NoFraming` streaming strategy
|
||||
([#959](https://github.com/haskell-servant/servant/pull/959))
|
||||
|
||||
- *servant-server* File serving in polymorphic monad.
|
||||
i.e. Generalised types of `serveDirectoryFileServer` etc functions in
|
||||
`Servant.Utils.StaticFiles`
|
||||
([#953](https://github.com/haskell-servant/servant/pull/953))
|
||||
|
||||
- *servant-server* `ReqBody` content type check is recoverable.
|
||||
This allows writing APIs like:
|
||||
|
||||
```haskell
|
||||
ReqBody '[JSON] Int :> Post '[PlainText] Int
|
||||
:<|> ReqBody '[PlainText] Int :> Post '[PlainText] Int
|
||||
```
|
||||
|
||||
which is useful when handlers are subtly different,
|
||||
for example may do less work.
|
||||
([#937](https://github.com/haskell-servant/servant/pull/937))
|
||||
|
||||
|
||||
0.13.0.1
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
name: servant-server
|
||||
version: 0.14
|
||||
version: 0.14.1
|
||||
x-revision: 1
|
||||
|
||||
synopsis: A family of combinators for defining webservices APIs and serving them
|
||||
category: Servant, Web
|
||||
description:
|
||||
A family of combinators for defining webservices APIs and serving them
|
||||
.
|
||||
|
@ -11,6 +14,7 @@ description:
|
|||
a webserver that serves this API, using this package.
|
||||
.
|
||||
<https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md CHANGELOG>
|
||||
|
||||
homepage: http://haskell-servant.readthedocs.org/
|
||||
Bug-reports: http://github.com/haskell-servant/servant/issues
|
||||
license: BSD3
|
||||
|
@ -18,7 +22,6 @@ license-file: LICENSE
|
|||
author: Servant Contributors
|
||||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
copyright: 2014-2016 Zalora South East Asia Pte Ltd, Servant Contributors
|
||||
category: Servant, Web
|
||||
build-type: Custom
|
||||
cabal-version: >=1.10
|
||||
tested-with:
|
||||
|
@ -26,7 +29,9 @@ tested-with:
|
|||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.3
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
extra-source-files:
|
||||
include/*.h
|
||||
CHANGELOG.md
|
||||
|
@ -40,13 +45,14 @@ custom-setup
|
|||
setup-depends:
|
||||
base >= 4 && <5,
|
||||
Cabal,
|
||||
cabal-doctest >= 1.0.1 && <1.1
|
||||
cabal-doctest >= 1.0.6 && <1.1
|
||||
|
||||
library
|
||||
exposed-modules:
|
||||
Servant
|
||||
Servant.Server
|
||||
Servant.Server.Experimental.Auth
|
||||
Servant.Server.Generic
|
||||
Servant.Server.Internal
|
||||
Servant.Server.Internal.BasicAuth
|
||||
Servant.Server.Internal.Context
|
||||
|
@ -54,6 +60,10 @@ library
|
|||
Servant.Server.Internal.Router
|
||||
Servant.Server.Internal.RoutingApplication
|
||||
Servant.Server.Internal.ServantErr
|
||||
Servant.Server.StaticFiles
|
||||
|
||||
-- deprecated
|
||||
exposed-modules:
|
||||
Servant.Utils.StaticFiles
|
||||
|
||||
-- Bundled with GHC: Lower bound to not force re-installs
|
||||
|
@ -61,9 +71,9 @@ library
|
|||
--
|
||||
-- note: mtl lower bound is so low because of GHC-7.8
|
||||
build-depends:
|
||||
base >= 4.7 && < 4.12
|
||||
base >= 4.7 && < 4.13
|
||||
, bytestring >= 0.10.4.0 && < 0.11
|
||||
, containers >= 0.5.5.1 && < 0.6
|
||||
, containers >= 0.5.5.1 && < 0.7
|
||||
, mtl >= 2.1 && < 2.3
|
||||
, text >= 1.2.3.0 && < 1.3
|
||||
, transformers >= 0.3.0.0 && < 0.6
|
||||
|
@ -71,37 +81,31 @@ library
|
|||
|
||||
if !impl(ghc >= 8.0)
|
||||
build-depends:
|
||||
semigroups >= 0.18.3 && < 0.19
|
||||
semigroups >= 0.18.4 && < 0.19
|
||||
|
||||
-- Servant dependencies
|
||||
build-depends:
|
||||
servant == 0.14.*
|
||||
servant >= 0.14.1 && <0.15
|
||||
|
||||
-- Other dependencies: Lower bound around what is in the latest Stackage LTS.
|
||||
-- Here can be exceptions if we really need features from the newer versions.
|
||||
build-depends:
|
||||
aeson >= 1.2.3.0 && < 1.5
|
||||
, base-compat >= 0.9.3 && < 0.11
|
||||
, attoparsec >= 0.13.2.0 && < 0.14
|
||||
base-compat >= 0.10.1 && < 0.11
|
||||
, base64-bytestring >= 1.0.0.1 && < 1.1
|
||||
, exceptions >= 0.8.3 && < 0.11
|
||||
, http-api-data >= 0.3.7.1 && < 0.4
|
||||
, http-media >= 0.7.1.1 && < 0.8
|
||||
, http-types >= 0.12 && < 0.13
|
||||
, exceptions >= 0.10.0 && < 0.11
|
||||
, http-api-data >= 0.3.8.1 && < 0.4
|
||||
, http-media >= 0.7.1.2 && < 0.8
|
||||
, http-types >= 0.12.1 && < 0.13
|
||||
, network-uri >= 2.6.1.0 && < 2.7
|
||||
, monad-control >= 1.0.0.4 && < 1.1
|
||||
, network >= 2.6.3.2 && < 2.8
|
||||
, safe >= 0.3.15 && < 0.4
|
||||
, split >= 0.2.3.2 && < 0.3
|
||||
, monad-control >= 1.0.2.3 && < 1.1
|
||||
, network >= 2.6.3.5 && < 2.9
|
||||
, string-conversions >= 0.4.0.1 && < 0.5
|
||||
, system-filepath >= 0.4 && < 0.5
|
||||
, resourcet >= 1.1.9 && < 1.3
|
||||
, resourcet >= 1.1.11 && < 1.3
|
||||
, tagged >= 0.8.5 && < 0.9
|
||||
, transformers-base >= 0.4.4 && < 0.5
|
||||
, transformers-compat >= 0.5.1 && < 0.7
|
||||
, transformers-compat >= 0.6.2 && < 0.7
|
||||
, wai >= 3.2.1.1 && < 3.3
|
||||
, wai-app-static >= 3.1.6.1 && < 3.2
|
||||
, warp >= 3.2.13 && < 3.3
|
||||
, word8 >= 0.1.3 && < 0.2
|
||||
|
||||
hs-source-dirs: src
|
||||
|
@ -120,11 +124,13 @@ executable greet
|
|||
base
|
||||
, servant
|
||||
, servant-server
|
||||
, aeson
|
||||
, warp
|
||||
, wai
|
||||
, text
|
||||
|
||||
build-depends:
|
||||
aeson >= 1.3.1.1 && < 1.5
|
||||
, warp >= 3.2.13 && < 3.3
|
||||
|
||||
test-suite spec
|
||||
type: exitcode-stdio-1.0
|
||||
ghc-options: -Wall
|
||||
|
@ -137,24 +143,21 @@ test-suite spec
|
|||
Servant.Server.Internal.ContextSpec
|
||||
Servant.Server.Internal.RoutingApplicationSpec
|
||||
Servant.Server.RouterSpec
|
||||
Servant.Server.StaticFilesSpec
|
||||
Servant.Server.StreamingSpec
|
||||
Servant.Server.UsingContextSpec
|
||||
Servant.Server.UsingContextSpec.TestCombinators
|
||||
Servant.HoistSpec
|
||||
Servant.ServerSpec
|
||||
Servant.Utils.StaticFilesSpec
|
||||
|
||||
-- Dependencies inherited from the library. No need to specify bounds.
|
||||
build-depends:
|
||||
base
|
||||
, base-compat
|
||||
, aeson
|
||||
, base64-bytestring
|
||||
, bytestring
|
||||
, exceptions
|
||||
, http-types
|
||||
, mtl
|
||||
, network
|
||||
, resourcet
|
||||
, safe
|
||||
, servant
|
||||
|
@ -164,27 +167,26 @@ test-suite spec
|
|||
, transformers
|
||||
, transformers-compat
|
||||
, wai
|
||||
, warp
|
||||
|
||||
-- Additonal dependencies
|
||||
build-depends:
|
||||
directory >= 1.2.1.0 && < 1.4
|
||||
, hspec >= 2.4.4 && < 2.6
|
||||
, hspec-wai >= 0.9 && < 0.10
|
||||
aeson >= 1.3.1.1 && < 1.5
|
||||
, directory >= 1.2.1.0 && < 1.4
|
||||
, hspec >= 2.5.1 && < 2.6
|
||||
, hspec-wai >= 0.9.0 && < 0.10
|
||||
, QuickCheck >= 2.11.3 && < 2.13
|
||||
, should-not-typecheck >= 2.1.0 && < 2.2
|
||||
, parsec >= 3.1.11 && < 3.2
|
||||
, QuickCheck >= 2.10.1 && < 2.12
|
||||
, temporary >= 1.3 && < 1.4
|
||||
, wai-extra >= 3.0.21.0 && < 3.1
|
||||
, temporary >= 1.2.0.3 && < 1.4
|
||||
|
||||
build-tool-depends:
|
||||
hspec-discover:hspec-discover >=2.4.4 && <2.6
|
||||
hspec-discover:hspec-discover >= 2.5.1 && <2.6
|
||||
|
||||
test-suite doctests
|
||||
build-depends:
|
||||
base
|
||||
, servant-server
|
||||
, doctest >= 0.13.0 && <0.16
|
||||
, doctest >= 0.15.0 && <0.17
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: test/doctests.hs
|
||||
buildable: True
|
||||
|
|
|
@ -6,7 +6,7 @@ module Servant (
|
|||
-- | For implementing servers for servant APIs.
|
||||
module Servant.Server,
|
||||
-- | Utilities on top of the servant core
|
||||
module Servant.Utils.Links,
|
||||
module Servant.Links,
|
||||
module Servant.Utils.StaticFiles,
|
||||
-- | Useful re-exports
|
||||
Proxy(..),
|
||||
|
@ -17,5 +17,5 @@ import Control.Monad.Error.Class (throwError)
|
|||
import Data.Proxy
|
||||
import Servant.API
|
||||
import Servant.Server
|
||||
import Servant.Utils.Links
|
||||
import Servant.Links
|
||||
import Servant.Utils.StaticFiles
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
{-# LANGUAGE ConstraintKinds #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE KindSignatures #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
-- | @since 0.14.1
|
||||
module Servant.Server.Generic (
|
||||
AsServerT,
|
||||
AsServer,
|
||||
genericServe,
|
||||
genericServer,
|
||||
genericServerT,
|
||||
) where
|
||||
|
||||
import Data.Proxy
|
||||
(Proxy (..))
|
||||
|
||||
import Servant.API.Generic
|
||||
import Servant.Server
|
||||
|
||||
-- | A type that specifies that an API record contains a server implementation.
|
||||
data AsServerT (m :: * -> *)
|
||||
instance GenericMode (AsServerT m) where
|
||||
type AsServerT m :- api = ServerT api m
|
||||
|
||||
type AsServer = AsServerT Handler
|
||||
|
||||
-- | Transform record of routes into a WAI 'Application'.
|
||||
genericServe
|
||||
:: forall routes.
|
||||
( HasServer (ToServantApi routes) '[]
|
||||
, GenericServant routes AsServer
|
||||
, Server (ToServantApi routes) ~ ToServant routes AsServer
|
||||
)
|
||||
=> routes AsServer -> Application
|
||||
genericServe = serve (Proxy :: Proxy (ToServantApi routes)) . genericServer
|
||||
|
||||
-- | Transform record of endpoints into a 'Server'.
|
||||
genericServer
|
||||
:: GenericServant routes AsServer
|
||||
=> routes AsServer
|
||||
-> ToServant routes AsServer
|
||||
genericServer = toServant
|
||||
|
||||
genericServerT
|
||||
:: GenericServant routes (AsServerT m)
|
||||
=> routes (AsServerT m)
|
||||
-> ToServant routes (AsServerT m)
|
||||
genericServerT = toServant
|
|
@ -18,7 +18,7 @@ import GHC.TypeLits
|
|||
-- | 'Context's are used to pass values to combinators. (They are __not__ meant
|
||||
-- to be used to pass parameters to your handlers, i.e. they should not replace
|
||||
-- any custom 'Control.Monad.Trans.Reader.ReaderT'-monad-stack that you're using
|
||||
-- with 'Servant.Utils.Enter'.) If you don't use combinators that
|
||||
-- with 'hoistServer'.) If you don't use combinators that
|
||||
-- require any context entries, you can just use 'Servant.Server.serve' as always.
|
||||
--
|
||||
-- If you are using combinators that require a non-empty 'Context' you have to
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
{-# LANGUAGE CPP #-}
|
||||
-- | This module defines server-side handlers that lets you serve static files.
|
||||
--
|
||||
-- The most common needs for a web application are covered by
|
||||
-- 'serveDirectoryWebApp`, but the other variants allow you to use
|
||||
-- different `StaticSettings` and 'serveDirectoryWith' even allows you
|
||||
-- to specify arbitrary 'StaticSettings' to be used for serving static files.
|
||||
module Servant.Server.StaticFiles
|
||||
( serveDirectoryWebApp
|
||||
, serveDirectoryWebAppLookup
|
||||
, serveDirectoryFileServer
|
||||
, serveDirectoryEmbedded
|
||||
, serveDirectoryWith
|
||||
, -- * Deprecated
|
||||
serveDirectory
|
||||
) where
|
||||
|
||||
import Data.ByteString
|
||||
(ByteString)
|
||||
import Network.Wai.Application.Static
|
||||
import Servant.API.Raw
|
||||
(Raw)
|
||||
import Servant.Server
|
||||
(ServerT, Tagged (..))
|
||||
import System.FilePath
|
||||
(addTrailingPathSeparator)
|
||||
#if !MIN_VERSION_wai_app_static(3,1,0)
|
||||
import Filesystem.Path.CurrentOS
|
||||
(decodeString)
|
||||
#endif
|
||||
import WaiAppStatic.Storage.Filesystem
|
||||
(ETagLookup)
|
||||
|
||||
-- | Serve anything under the specified directory as a 'Raw' endpoint.
|
||||
--
|
||||
-- @
|
||||
-- type MyApi = "static" :> Raw
|
||||
--
|
||||
-- server :: Server MyApi
|
||||
-- server = serveDirectoryWebApp "\/var\/www"
|
||||
-- @
|
||||
--
|
||||
-- would capture any request to @\/static\/\<something>@ and look for
|
||||
-- @\<something>@ under @\/var\/www@.
|
||||
--
|
||||
-- It will do its best to guess the MIME type for that file, based on the extension,
|
||||
-- and send an appropriate /Content-Type/ header if possible.
|
||||
--
|
||||
-- If your goal is to serve HTML, CSS and Javascript files that use the rest of the API
|
||||
-- as a webapp backend, you will most likely not want the static files to be hidden
|
||||
-- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectoryWebApp'
|
||||
-- handler in the last position, because /servant/ will try to match the handlers
|
||||
-- in order.
|
||||
--
|
||||
-- Corresponds to the `defaultWebAppSettings` `StaticSettings` value.
|
||||
serveDirectoryWebApp :: FilePath -> ServerT Raw m
|
||||
serveDirectoryWebApp = serveDirectoryWith . defaultWebAppSettings . fixPath
|
||||
|
||||
-- | Same as 'serveDirectoryWebApp', but uses `defaultFileServerSettings`.
|
||||
serveDirectoryFileServer :: FilePath -> ServerT Raw m
|
||||
serveDirectoryFileServer = serveDirectoryWith . defaultFileServerSettings . fixPath
|
||||
|
||||
-- | Same as 'serveDirectoryWebApp', but uses 'webAppSettingsWithLookup'.
|
||||
serveDirectoryWebAppLookup :: ETagLookup -> FilePath -> ServerT Raw m
|
||||
serveDirectoryWebAppLookup etag =
|
||||
serveDirectoryWith . flip webAppSettingsWithLookup etag . fixPath
|
||||
|
||||
-- | Uses 'embeddedSettings'.
|
||||
serveDirectoryEmbedded :: [(FilePath, ByteString)] -> ServerT Raw m
|
||||
serveDirectoryEmbedded files = serveDirectoryWith (embeddedSettings files)
|
||||
|
||||
-- | Alias for 'staticApp'. Lets you serve a directory
|
||||
-- with arbitrary 'StaticSettings'. Useful when you want
|
||||
-- particular settings not covered by the four other
|
||||
-- variants. This is the most flexible method.
|
||||
serveDirectoryWith :: StaticSettings -> ServerT Raw m
|
||||
serveDirectoryWith = Tagged . staticApp
|
||||
|
||||
-- | Same as 'serveDirectoryFileServer'. It used to be the only
|
||||
-- file serving function in servant pre-0.10 and will be kept
|
||||
-- around for a few versions, but is deprecated.
|
||||
serveDirectory :: FilePath -> ServerT Raw m
|
||||
serveDirectory = serveDirectoryFileServer
|
||||
{-# DEPRECATED serveDirectory "Use serveDirectoryFileServer instead" #-}
|
||||
|
||||
fixPath :: FilePath -> FilePath
|
||||
fixPath =
|
||||
#if MIN_VERSION_wai_app_static(3,1,0)
|
||||
addTrailingPathSeparator
|
||||
#else
|
||||
decodeString . addTrailingPathSeparator
|
||||
#endif
|
|
@ -1,86 +1,6 @@
|
|||
{-# LANGUAGE CPP #-}
|
||||
-- | This module defines server-side handlers that lets you serve static files.
|
||||
--
|
||||
-- The most common needs for a web application are covered by
|
||||
-- 'serveDirectoryWebApp`, but the other variants allow you to use
|
||||
-- different `StaticSettings` and 'serveDirectoryWith' even allows you
|
||||
-- to specify arbitrary 'StaticSettings' to be used for serving static files.
|
||||
module Servant.Utils.StaticFiles
|
||||
( serveDirectoryWebApp
|
||||
, serveDirectoryWebAppLookup
|
||||
, serveDirectoryFileServer
|
||||
, serveDirectoryEmbedded
|
||||
, serveDirectoryWith
|
||||
, -- * Deprecated
|
||||
serveDirectory
|
||||
) where
|
||||
{-# DEPRECATED "Use Servant.ServerStaticFiles." #-}
|
||||
( module Servant.Server.StaticFiles )
|
||||
where
|
||||
|
||||
import Data.ByteString (ByteString)
|
||||
import Network.Wai.Application.Static
|
||||
import Servant.API.Raw (Raw)
|
||||
import Servant.Server (ServerT, Tagged (..))
|
||||
import System.FilePath (addTrailingPathSeparator)
|
||||
#if !MIN_VERSION_wai_app_static(3,1,0)
|
||||
import Filesystem.Path.CurrentOS (decodeString)
|
||||
#endif
|
||||
import WaiAppStatic.Storage.Filesystem (ETagLookup)
|
||||
|
||||
-- | Serve anything under the specified directory as a 'Raw' endpoint.
|
||||
--
|
||||
-- @
|
||||
-- type MyApi = "static" :> Raw
|
||||
--
|
||||
-- server :: Server MyApi
|
||||
-- server = serveDirectoryWebApp "\/var\/www"
|
||||
-- @
|
||||
--
|
||||
-- would capture any request to @\/static\/\<something>@ and look for
|
||||
-- @\<something>@ under @\/var\/www@.
|
||||
--
|
||||
-- It will do its best to guess the MIME type for that file, based on the extension,
|
||||
-- and send an appropriate /Content-Type/ header if possible.
|
||||
--
|
||||
-- If your goal is to serve HTML, CSS and Javascript files that use the rest of the API
|
||||
-- as a webapp backend, you will most likely not want the static files to be hidden
|
||||
-- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectoryWebApp'
|
||||
-- handler in the last position, because /servant/ will try to match the handlers
|
||||
-- in order.
|
||||
--
|
||||
-- Corresponds to the `defaultWebAppSettings` `StaticSettings` value.
|
||||
serveDirectoryWebApp :: FilePath -> ServerT Raw m
|
||||
serveDirectoryWebApp = serveDirectoryWith . defaultWebAppSettings . fixPath
|
||||
|
||||
-- | Same as 'serveDirectoryWebApp', but uses `defaultFileServerSettings`.
|
||||
serveDirectoryFileServer :: FilePath -> ServerT Raw m
|
||||
serveDirectoryFileServer = serveDirectoryWith . defaultFileServerSettings . fixPath
|
||||
|
||||
-- | Same as 'serveDirectoryWebApp', but uses 'webAppSettingsWithLookup'.
|
||||
serveDirectoryWebAppLookup :: ETagLookup -> FilePath -> ServerT Raw m
|
||||
serveDirectoryWebAppLookup etag =
|
||||
serveDirectoryWith . flip webAppSettingsWithLookup etag . fixPath
|
||||
|
||||
-- | Uses 'embeddedSettings'.
|
||||
serveDirectoryEmbedded :: [(FilePath, ByteString)] -> ServerT Raw m
|
||||
serveDirectoryEmbedded files = serveDirectoryWith (embeddedSettings files)
|
||||
|
||||
-- | Alias for 'staticApp'. Lets you serve a directory
|
||||
-- with arbitrary 'StaticSettings'. Useful when you want
|
||||
-- particular settings not covered by the four other
|
||||
-- variants. This is the most flexible method.
|
||||
serveDirectoryWith :: StaticSettings -> ServerT Raw m
|
||||
serveDirectoryWith = Tagged . staticApp
|
||||
|
||||
-- | Same as 'serveDirectoryFileServer'. It used to be the only
|
||||
-- file serving function in servant pre-0.10 and will be kept
|
||||
-- around for a few versions, but is deprecated.
|
||||
serveDirectory :: FilePath -> ServerT Raw m
|
||||
serveDirectory = serveDirectoryFileServer
|
||||
{-# DEPRECATED serveDirectory "Use serveDirectoryFileServer instead" #-}
|
||||
|
||||
fixPath :: FilePath -> FilePath
|
||||
fixPath =
|
||||
#if MIN_VERSION_wai_app_static(3,1,0)
|
||||
addTrailingPathSeparator
|
||||
#else
|
||||
decodeString . addTrailingPathSeparator
|
||||
#endif
|
||||
import Servant.Server.StaticFiles
|
||||
|
|
|
@ -3,22 +3,31 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
module Servant.Utils.StaticFilesSpec where
|
||||
module Servant.Server.StaticFilesSpec where
|
||||
|
||||
import Control.Exception (bracket)
|
||||
import Data.Proxy (Proxy (Proxy))
|
||||
import Network.Wai (Application)
|
||||
import System.Directory (createDirectory,
|
||||
getCurrentDirectory,
|
||||
setCurrentDirectory)
|
||||
import System.IO.Temp (withSystemTempDirectory)
|
||||
import Test.Hspec (Spec, around_, describe, it)
|
||||
import Test.Hspec.Wai (get, shouldRespondWith, with)
|
||||
import Control.Exception
|
||||
(bracket)
|
||||
import Data.Proxy
|
||||
(Proxy (Proxy))
|
||||
import Network.Wai
|
||||
(Application)
|
||||
import System.Directory
|
||||
(createDirectory, getCurrentDirectory, setCurrentDirectory)
|
||||
import System.IO.Temp
|
||||
(withSystemTempDirectory)
|
||||
import Test.Hspec
|
||||
(Spec, around_, describe, it)
|
||||
import Test.Hspec.Wai
|
||||
(get, shouldRespondWith, with)
|
||||
|
||||
import Servant.API ((:<|>) ((:<|>)), Capture, Get, Raw, (:>), JSON)
|
||||
import Servant.Server (Server, serve)
|
||||
import Servant.ServerSpec (Person (Person))
|
||||
import Servant.Utils.StaticFiles (serveDirectoryFileServer)
|
||||
import Servant.API
|
||||
((:<|>) ((:<|>)), (:>), Capture, Get, JSON, Raw)
|
||||
import Servant.Server
|
||||
(Server, serve)
|
||||
import Servant.Server.StaticFiles
|
||||
(serveDirectoryFileServer)
|
||||
import Servant.ServerSpec
|
||||
(Person (Person))
|
||||
|
||||
type Api =
|
||||
"dummy_api" :> Capture "person_name" String :> Get '[JSON] Person
|
|
@ -1,5 +1,18 @@
|
|||
[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
|
||||
|
||||
0.14.1
|
||||
------
|
||||
|
||||
- Merge in (and slightly refactor) `servant-generic`
|
||||
(by [Patrick Chilton](https://github.com/chpatrick))
|
||||
into `servant` (`Servant.API.Generic`),
|
||||
`servant-client-code` (`Servant.Client.Generic`)
|
||||
and `servant-server` (`Servant.Server.Generic`).
|
||||
|
||||
- Deprecate `Servant.Utils.Links`, use `Servant.Links`.
|
||||
|
||||
- *servant-server* Deprecate `Servant.Utils.StaticUtils`, use `Servant.Server.StaticUtils`.
|
||||
|
||||
0.14
|
||||
----
|
||||
|
||||
|
@ -30,16 +43,25 @@
|
|||
- Added `NoFraming` streaming strategy
|
||||
([#959](https://github.com/haskell-servant/servant/pull/959))
|
||||
|
||||
- *servant-client* Free `Client` implementation.
|
||||
- *servant-client-core* Free `Client` implementation.
|
||||
Useful for testing `HasClient` instances.
|
||||
([#920](https://github.com/haskell-servant/servant/pull/920))
|
||||
|
||||
- *servant-client* Add `hoistClient` to `HasClient`.
|
||||
- *servant-client-core* Add `hoistClient` to `HasClient`.
|
||||
Just like `hoistServer` allows us to change the monad in which request handlers
|
||||
of a web application live in, we also have `hoistClient` for changing the monad
|
||||
in which *client functions* live. Read [tutorial section for more information](#link).
|
||||
in which *client functions* live.
|
||||
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).
|
||||
([#936](https://github.com/haskell-servant/servant/pull/936))
|
||||
|
||||
iF you have own combinators, you'll need to define a new method of
|
||||
`HasClient` class, for example:
|
||||
|
||||
```haskell
|
||||
type Client m (MyCombinator :> api) = MyValue :> Client m api
|
||||
hoistClientMonad pm _ nt cl = hoistClientMonad pm (Proxy :: Proxy api) nt . cl
|
||||
```
|
||||
|
||||
- *servant* Add `safeLink' :: (Link -> a) -> ... -> MkLink endpoint a`,
|
||||
which allows to create helpers returning something else than `Link`.
|
||||
([#968](https://github.com/haskell-servant/servant/pull/968))
|
||||
|
@ -76,10 +98,10 @@
|
|||
- Added tests or enabled tests
|
||||
([#975](https://github.com/haskell-servant/servant/pull/975))
|
||||
|
||||
- Add [pagination cookbook recipe](#link)
|
||||
- Add [pagination cookbook recipe](https://haskell-servant.readthedocs.io/en/release-0.14/cookbook/pagination/Pagination.html)
|
||||
([#946](https://github.com/haskell-servant/servant/pull/946))
|
||||
|
||||
- Add [`servant-flatten` cookbook recipe](#link)
|
||||
- Add [`servant-flatten` "spice" to the structuring api recipe](https://haskell-servant.readthedocs.io/en/release-0.14/cookbook/structuring-apis/StructuringApis.html)
|
||||
([#929](https://github.com/haskell-servant/servant/pull/929))
|
||||
|
||||
- Dependency updates
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
name: servant
|
||||
version: 0.14
|
||||
version: 0.14.1
|
||||
x-revision: 1
|
||||
|
||||
synopsis: A family of combinators for defining webservices APIs
|
||||
category: Servant, Web
|
||||
description:
|
||||
A family of combinators for defining webservices APIs and serving them
|
||||
.
|
||||
You can learn about the basics in the <http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html tutorial>.
|
||||
.
|
||||
<https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md CHANGELOG>
|
||||
|
||||
homepage: http://haskell-servant.readthedocs.org/
|
||||
Bug-reports: http://github.com/haskell-servant/servant/issues
|
||||
license: BSD3
|
||||
|
@ -14,7 +18,6 @@ license-file: LICENSE
|
|||
author: Servant Contributors
|
||||
maintainer: haskell-servant-maintainers@googlegroups.com
|
||||
copyright: 2014-2016 Zalora South East Asia Pte Ltd, Servant Contributors
|
||||
category: Servant, Web
|
||||
build-type: Custom
|
||||
cabal-version: >=1.10
|
||||
tested-with:
|
||||
|
@ -22,7 +25,9 @@ tested-with:
|
|||
GHC==7.10.3
|
||||
GHC==8.0.2
|
||||
GHC==8.2.2
|
||||
GHC==8.4.3
|
||||
GHC==8.4.4
|
||||
GHC==8.6.1
|
||||
|
||||
extra-source-files:
|
||||
include/*.h
|
||||
CHANGELOG.md
|
||||
|
@ -34,7 +39,7 @@ custom-setup
|
|||
setup-depends:
|
||||
base >= 4 && <5,
|
||||
Cabal,
|
||||
cabal-doctest >= 1.0.2 && <1.1
|
||||
cabal-doctest >= 1.0.6 && <1.1
|
||||
|
||||
library
|
||||
exposed-modules:
|
||||
|
@ -46,6 +51,7 @@ library
|
|||
Servant.API.Description
|
||||
Servant.API.Empty
|
||||
Servant.API.Experimental.Auth
|
||||
Servant.API.Generic
|
||||
Servant.API.Header
|
||||
Servant.API.HttpVersion
|
||||
Servant.API.Internal.Test.ComprehensiveAPI
|
||||
|
@ -62,6 +68,10 @@ library
|
|||
Servant.API.Vault
|
||||
Servant.API.Verbs
|
||||
Servant.API.WithNamedContext
|
||||
Servant.Links
|
||||
|
||||
-- Deprecated modules, to be removed in late 2019
|
||||
exposed-modules:
|
||||
Servant.Utils.Links
|
||||
Servant.Utils.Enter
|
||||
|
||||
|
@ -70,32 +80,32 @@ library
|
|||
--
|
||||
-- note: mtl lower bound is so low because of GHC-7.8
|
||||
build-depends:
|
||||
base >= 4.7 && < 4.12
|
||||
base >= 4.7 && < 4.13
|
||||
, bytestring >= 0.10.4.0 && < 0.11
|
||||
, mtl >= 2.1 && < 2.3
|
||||
, text >= 1.2.3.0 && < 1.3
|
||||
|
||||
if !impl(ghc >= 8.0)
|
||||
build-depends:
|
||||
semigroups >= 0.18.3 && < 0.19
|
||||
semigroups >= 0.18.4 && < 0.19
|
||||
|
||||
-- Other dependencies: Lower bound around what is in the latest Stackage LTS.
|
||||
-- Here can be exceptions if we really need features from the newer versions.
|
||||
build-depends:
|
||||
base-compat >= 0.9.3 && < 0.11
|
||||
, aeson >= 1.2.3.0 && < 1.5
|
||||
, attoparsec >= 0.13.2.0 && < 0.14
|
||||
base-compat >= 0.10.1 && < 0.11
|
||||
, aeson >= 1.3.1.1 && < 1.5
|
||||
, attoparsec >= 0.13.2.2 && < 0.14
|
||||
, case-insensitive >= 1.2.0.10 && < 1.3
|
||||
, http-api-data >= 0.3.7.1 && < 0.4
|
||||
, http-media >= 0.7.1.1 && < 0.8
|
||||
, http-types >= 0.12 && < 0.13
|
||||
, http-api-data >= 0.3.8.1 && < 0.4
|
||||
, http-media >= 0.7.1.2 && < 0.8
|
||||
, http-types >= 0.12.1 && < 0.13
|
||||
, natural-transformation >= 0.4 && < 0.5
|
||||
, mmorph >= 1.1.0 && < 1.2
|
||||
, mmorph >= 1.1.2 && < 1.2
|
||||
, tagged >= 0.8.5 && < 0.9
|
||||
, singleton-bool >= 0.1.2.0 && < 0.2
|
||||
, singleton-bool >= 0.1.4 && < 0.2
|
||||
, string-conversions >= 0.4.0.1 && < 0.5
|
||||
, network-uri >= 2.6.1.0 && < 2.7
|
||||
, vault >= 0.3.0.7 && < 0.4
|
||||
, vault >= 0.3.1.1 && < 0.4
|
||||
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
|
@ -133,15 +143,14 @@ test-suite spec
|
|||
other-modules:
|
||||
Servant.API.ContentTypesSpec
|
||||
Servant.API.ResponseHeadersSpec
|
||||
Servant.Utils.LinksSpec
|
||||
Servant.Utils.EnterSpec
|
||||
Servant.LinksSpec
|
||||
|
||||
-- Dependencies inherited from the library. No need to specify bounds.
|
||||
build-depends:
|
||||
base
|
||||
, base-compat
|
||||
, aeson
|
||||
, attoparsec
|
||||
, bytestring
|
||||
, servant
|
||||
, string-conversions
|
||||
|
@ -153,23 +162,23 @@ test-suite spec
|
|||
|
||||
-- Additonal dependencies
|
||||
build-depends:
|
||||
aeson-compat >= 0.3.3 && < 0.4
|
||||
, hspec >= 2.4.4 && < 2.6
|
||||
, QuickCheck >= 2.10.1 && < 2.12
|
||||
, quickcheck-instances >= 0.3.16 && < 0.4
|
||||
aeson-compat >= 0.3.7.1 && < 0.4
|
||||
, hspec >= 2.5.1 && < 2.6
|
||||
, QuickCheck >= 2.11.3 && < 2.13
|
||||
, quickcheck-instances >= 0.3.18 && < 0.4
|
||||
|
||||
build-tool-depends:
|
||||
hspec-discover:hspec-discover >= 2.4.4 && < 2.6
|
||||
hspec-discover:hspec-discover >= 2.5.1 && < 2.6
|
||||
|
||||
test-suite doctests
|
||||
build-depends:
|
||||
base
|
||||
, servant
|
||||
, doctest >= 0.13.0 && <0.16
|
||||
, doctest >= 0.15.0 && <0.17
|
||||
|
||||
-- We test Links failure with doctest, so we need extra dependencies
|
||||
build-depends:
|
||||
hspec >= 2.4.4 && < 2.6
|
||||
hspec >= 2.5.1 && < 2.6
|
||||
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: test/doctests.hs
|
||||
|
@ -180,4 +189,4 @@ test-suite doctests
|
|||
x-doctest-options: -fdiagnostics-color=never
|
||||
include-dirs: include
|
||||
x-doctest-source-dirs: test
|
||||
x-doctest-modules: Servant.Utils.LinksSpec
|
||||
x-doctest-modules: Servant.LinksSpec
|
||||
|
|
|
@ -63,8 +63,8 @@ module Servant.API (
|
|||
module Servant.API.Experimental.Auth,
|
||||
-- | General Authentication
|
||||
|
||||
-- * Utilities
|
||||
module Servant.Utils.Links,
|
||||
-- * Links
|
||||
module Servant.Links,
|
||||
-- | Type-safe internal URIs
|
||||
|
||||
-- * Re-exports
|
||||
|
@ -134,7 +134,7 @@ import Servant.API.Verbs
|
|||
ReflectMethod (reflectMethod), StdMethod (..), Verb)
|
||||
import Servant.API.WithNamedContext
|
||||
(WithNamedContext)
|
||||
import Servant.Utils.Links
|
||||
import Servant.Links
|
||||
(HasLink (..), IsElem, IsElem', Link, URI (..), safeLink)
|
||||
import Web.HttpApiData
|
||||
(FromHttpApiData (..), ToHttpApiData (..))
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
{-# LANGUAGE ConstraintKinds #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
-- | Define servant servers from record types. Generics for the win.
|
||||
--
|
||||
-- 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`:
|
||||
--
|
||||
-- @
|
||||
-- data Routes route = Routes
|
||||
-- { _get :: route :- Capture "id" Int :> Get '[JSON] String
|
||||
-- , _put :: route :- ReqBody '[JSON] Int :> Put '[JSON] Bool
|
||||
-- }
|
||||
-- deriving ('Generic')
|
||||
-- @
|
||||
--
|
||||
-- You can get a 'Proxy' of the server using
|
||||
--
|
||||
-- @
|
||||
-- api :: Proxy (ToServantApi Routes)
|
||||
-- api = genericApi (Proxy :: Proxy Routes)
|
||||
-- @
|
||||
--
|
||||
-- Using 'genericApi' is better as it checks that instances exists,
|
||||
-- i.e. you get better error messages than simply using 'Proxy' value.
|
||||
--
|
||||
-- __Note:__ in 0.14 series this module isn't re-exported from 'Servant.API'.
|
||||
--
|
||||
-- "Servant.API.Generic" is based on @servant-generic@ package by
|
||||
-- [Patrick Chilton](https://github.com/chpatrick)
|
||||
--
|
||||
-- @since 0.14.1
|
||||
module Servant.API.Generic (
|
||||
GenericMode (..),
|
||||
GenericServant,
|
||||
ToServant,
|
||||
toServant,
|
||||
fromServant,
|
||||
-- * AsApi
|
||||
AsApi,
|
||||
ToServantApi,
|
||||
genericApi,
|
||||
-- * Utility
|
||||
GServantProduct,
|
||||
-- * re-exports
|
||||
Generic (Rep),
|
||||
) where
|
||||
|
||||
-- Based on servant-generic licensed under MIT License
|
||||
--
|
||||
-- Copyright (c) 2017 Patrick Chilton
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
-- of this software and associated documentation files (the "Software"), to deal
|
||||
-- in the Software without restriction, including without limitation the rights
|
||||
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
-- copies of the Software, and to permit persons to whom the Software is
|
||||
-- furnished to do so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
|
||||
import Data.Proxy
|
||||
(Proxy (..))
|
||||
import GHC.Generics
|
||||
((:*:) (..), Generic (..), K1 (..), M1 (..))
|
||||
|
||||
import Servant.API.Alternative
|
||||
|
||||
-- | A constraint alias, for work with 'mode' and 'routes'.
|
||||
type GenericServant routes mode = (GenericMode mode, Generic (routes mode), GServantProduct (Rep (routes mode)))
|
||||
|
||||
-- | A class with a type family that applies an appropriate type family to the @api@
|
||||
-- parameter. For example, 'AsApi' will leave @api@ untouched, while
|
||||
-- @'AsServerT' m@ will produce @'ServerT' api m@.
|
||||
class GenericMode mode where
|
||||
type mode :- api :: *
|
||||
|
||||
infixl 0 :-
|
||||
|
||||
-- | Turns a generic product type into a tree of `:<|>` combinators.
|
||||
type ToServant routes mode = GToServant (Rep (routes mode))
|
||||
|
||||
type ToServantApi routes = ToServant routes AsApi
|
||||
|
||||
-- | See `ToServant`, but at value-level.
|
||||
toServant
|
||||
:: GenericServant routes mode
|
||||
=> routes mode -> ToServant routes mode
|
||||
toServant = gtoServant . from
|
||||
|
||||
-- | Inverse of `toServant`.
|
||||
--
|
||||
-- This can be used to turn 'generated' values such as client functions into records.
|
||||
--
|
||||
-- You may need to provide a type signature for the /output/ type (your record type).
|
||||
fromServant
|
||||
:: GenericServant routes mode
|
||||
=> ToServant routes mode -> routes mode
|
||||
fromServant = to . gfromServant
|
||||
|
||||
-- | A type that specifies that an API record contains an API definition. Only useful at type-level.
|
||||
data AsApi
|
||||
instance GenericMode AsApi where
|
||||
type AsApi :- api = api
|
||||
|
||||
-- | Get a 'Proxy' of an API type.
|
||||
genericApi
|
||||
:: GenericServant routes AsApi
|
||||
=> Proxy routes
|
||||
-> Proxy (ToServantApi routes)
|
||||
genericApi _ = Proxy
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Class
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class GServantProduct f where
|
||||
type GToServant f
|
||||
gtoServant :: f p -> GToServant f
|
||||
gfromServant :: GToServant f -> f p
|
||||
|
||||
instance GServantProduct f => GServantProduct (M1 i c f) where
|
||||
type GToServant (M1 i c f) = GToServant f
|
||||
gtoServant = gtoServant . unM1
|
||||
gfromServant = M1 . gfromServant
|
||||
|
||||
instance (GServantProduct l, GServantProduct r) => GServantProduct (l :*: r) where
|
||||
type GToServant (l :*: r) = GToServant l :<|> GToServant r
|
||||
gtoServant (l :*: r) = gtoServant l :<|> gtoServant r
|
||||
gfromServant (l :<|> r) = gfromServant l :*: gfromServant r
|
||||
|
||||
instance GServantProduct (K1 i c) where
|
||||
type GToServant (K1 i c) = c
|
||||
gtoServant = unK1
|
||||
gfromServant = K1
|
|
@ -58,7 +58,7 @@ type Patch = Verb 'PATCH 200
|
|||
--
|
||||
-- If the resource cannot be created immediately, use 'PostAccepted'.
|
||||
--
|
||||
-- Consider using 'Servant.Utils.Links.safeLink' for the @Location@ header
|
||||
-- Consider using 'Servant.Links.safeLink' for the @Location@ header
|
||||
-- field.
|
||||
|
||||
-- | 'POST' with 201 status code.
|
||||
|
|
|
@ -0,0 +1,573 @@
|
|||
{-# LANGUAGE ConstraintKinds #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE PolyKinds #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
{-# OPTIONS_HADDOCK not-home #-}
|
||||
|
||||
-- | Type safe generation of internal links.
|
||||
--
|
||||
-- Given an API with a few endpoints:
|
||||
--
|
||||
-- >>> :set -XDataKinds -XTypeFamilies -XTypeOperators
|
||||
-- >>> import Servant.API
|
||||
-- >>> import Servant.Links
|
||||
-- >>> import Data.Proxy
|
||||
-- >>>
|
||||
-- >>> type Hello = "hello" :> Get '[JSON] Int
|
||||
-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete '[JSON] NoContent
|
||||
-- >>> type API = Hello :<|> Bye
|
||||
-- >>> let api = Proxy :: Proxy API
|
||||
--
|
||||
-- It is possible to generate links that are guaranteed to be within 'API' with
|
||||
-- 'safeLink'. The first argument to 'safeLink' is a type representing the API
|
||||
-- you would like to restrict links to. The second argument is the destination
|
||||
-- endpoint you would like the link to point to, this will need to end with a
|
||||
-- verb like GET or POST. Further arguments may be required depending on the
|
||||
-- type of the endpoint. If everything lines up you will get a 'Link' out the
|
||||
-- other end.
|
||||
--
|
||||
-- You may omit 'QueryParam's and the like should you not want to provide them,
|
||||
-- but types which form part of the URL path like 'Capture' must be included.
|
||||
-- The reason you may want to omit 'QueryParam's is that safeLink is a bit
|
||||
-- magical: if parameters are included that could take input it will return a
|
||||
-- function that accepts that input and generates a link. This is best shown
|
||||
-- with an example. Here, a link is generated with no parameters:
|
||||
--
|
||||
-- >>> let hello = Proxy :: Proxy ("hello" :> Get '[JSON] Int)
|
||||
-- >>> toUrlPiece (safeLink api hello :: Link)
|
||||
-- "hello"
|
||||
--
|
||||
-- If the API has an endpoint with parameters then we can generate links with
|
||||
-- or without those:
|
||||
--
|
||||
-- >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete '[JSON] NoContent)
|
||||
-- >>> toUrlPiece $ safeLink api with (Just "Hubert")
|
||||
-- "bye?name=Hubert"
|
||||
--
|
||||
-- >>> let without = Proxy :: Proxy ("bye" :> Delete '[JSON] NoContent)
|
||||
-- >>> toUrlPiece $ safeLink api without
|
||||
-- "bye"
|
||||
--
|
||||
-- If you would like create a helper for generating links only within that API,
|
||||
-- you can partially apply safeLink if you specify a correct type signature
|
||||
-- like so:
|
||||
--
|
||||
-- >>> :set -XConstraintKinds
|
||||
-- >>> :{
|
||||
-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
|
||||
-- >>> => Proxy endpoint -> MkLink endpoint Link
|
||||
-- >>> apiLink = safeLink api
|
||||
-- >>> :}
|
||||
--
|
||||
-- `safeLink'` allows to make specialise the output:
|
||||
--
|
||||
-- >>> safeLink' toUrlPiece api without
|
||||
-- "bye"
|
||||
--
|
||||
-- >>> :{
|
||||
-- >>> let apiTextLink :: (IsElem endpoint API, HasLink endpoint)
|
||||
-- >>> => Proxy endpoint -> MkLink endpoint Text
|
||||
-- >>> apiTextLink = safeLink' toUrlPiece api
|
||||
-- >>> :}
|
||||
--
|
||||
-- >>> apiTextLink without
|
||||
-- "bye"
|
||||
--
|
||||
-- Attempting to construct a link to an endpoint that does not exist in api
|
||||
-- will result in a type error like this:
|
||||
--
|
||||
-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] NoContent)
|
||||
-- >>> safeLink api bad_link
|
||||
-- ...
|
||||
-- ...Could not deduce...
|
||||
-- ...
|
||||
--
|
||||
-- This error is essentially saying that the type family couldn't find
|
||||
-- bad_link under api after trying the open (but empty) type family
|
||||
-- `IsElem'` as a last resort.
|
||||
--
|
||||
-- @since 0.14.1
|
||||
module Servant.Links (
|
||||
module Servant.API.TypeLevel,
|
||||
|
||||
-- * Building and using safe links
|
||||
--
|
||||
-- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
|
||||
safeLink
|
||||
, safeLink'
|
||||
, allLinks
|
||||
, allLinks'
|
||||
, URI(..)
|
||||
-- * Generics
|
||||
, AsLink
|
||||
, fieldLink
|
||||
, fieldLink'
|
||||
, allFieldLinks
|
||||
, allFieldLinks'
|
||||
-- * Adding custom types
|
||||
, HasLink(..)
|
||||
, Link
|
||||
, linkURI
|
||||
, linkURI'
|
||||
, LinkArrayElementStyle (..)
|
||||
-- ** Link accessors
|
||||
, Param (..)
|
||||
, linkSegments
|
||||
, linkQueryParams
|
||||
) where
|
||||
|
||||
import Data.List
|
||||
import Data.Proxy
|
||||
(Proxy (..))
|
||||
import Data.Semigroup
|
||||
((<>))
|
||||
import Data.Singletons.Bool
|
||||
(SBool (..), SBoolI (..))
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import Data.Type.Bool
|
||||
(If)
|
||||
import GHC.TypeLits
|
||||
(KnownSymbol, symbolVal)
|
||||
import Network.URI
|
||||
(URI (..), escapeURIString, isUnreserved)
|
||||
import Prelude ()
|
||||
import Prelude.Compat
|
||||
|
||||
import Servant.API.Alternative
|
||||
((:<|>) ((:<|>)))
|
||||
import Servant.API.BasicAuth
|
||||
(BasicAuth)
|
||||
import Servant.API.Capture
|
||||
(Capture', CaptureAll)
|
||||
import Servant.API.Description
|
||||
(Description, Summary)
|
||||
import Servant.API.Empty
|
||||
(EmptyAPI (..))
|
||||
import Servant.API.Experimental.Auth
|
||||
(AuthProtect)
|
||||
import Servant.API.Generic
|
||||
import Servant.API.Header
|
||||
(Header')
|
||||
import Servant.API.HttpVersion
|
||||
(HttpVersion)
|
||||
import Servant.API.IsSecure
|
||||
(IsSecure)
|
||||
import Servant.API.Modifiers
|
||||
(FoldRequired)
|
||||
import Servant.API.QueryParam
|
||||
(QueryFlag, QueryParam', QueryParams)
|
||||
import Servant.API.Raw
|
||||
(Raw)
|
||||
import Servant.API.RemoteHost
|
||||
(RemoteHost)
|
||||
import Servant.API.ReqBody
|
||||
(ReqBody')
|
||||
import Servant.API.Stream
|
||||
(Stream)
|
||||
import Servant.API.Sub
|
||||
(type (:>))
|
||||
import Servant.API.TypeLevel
|
||||
import Servant.API.Vault
|
||||
(Vault)
|
||||
import Servant.API.Verbs
|
||||
(Verb)
|
||||
import Servant.API.WithNamedContext
|
||||
(WithNamedContext)
|
||||
import Web.HttpApiData
|
||||
|
||||
-- | A safe link datatype.
|
||||
-- The only way of constructing a 'Link' is using 'safeLink', which means any
|
||||
-- 'Link' is guaranteed to be part of the mentioned API.
|
||||
data Link = Link
|
||||
{ _segments :: [Escaped]
|
||||
, _queryParams :: [Param]
|
||||
} deriving Show
|
||||
|
||||
newtype Escaped = Escaped String
|
||||
|
||||
escaped :: String -> Escaped
|
||||
escaped = Escaped . escapeURIString isUnreserved
|
||||
|
||||
getEscaped :: Escaped -> String
|
||||
getEscaped (Escaped s) = s
|
||||
|
||||
instance Show Escaped where
|
||||
showsPrec d (Escaped s) = showsPrec d s
|
||||
show (Escaped s) = show s
|
||||
|
||||
linkSegments :: Link -> [String]
|
||||
linkSegments = map getEscaped . _segments
|
||||
|
||||
linkQueryParams :: Link -> [Param]
|
||||
linkQueryParams = _queryParams
|
||||
|
||||
instance ToHttpApiData Link where
|
||||
toHeader = TE.encodeUtf8 . toUrlPiece
|
||||
toUrlPiece l =
|
||||
let uri = linkURI l
|
||||
in Text.pack $ uriPath uri ++ uriQuery uri
|
||||
|
||||
-- | Query parameter.
|
||||
data Param
|
||||
= SingleParam String Text.Text
|
||||
| ArrayElemParam String Text.Text
|
||||
| FlagParam String
|
||||
deriving Show
|
||||
|
||||
addSegment :: Escaped -> Link -> Link
|
||||
addSegment seg l = l { _segments = _segments l <> [seg] }
|
||||
|
||||
addQueryParam :: Param -> Link -> Link
|
||||
addQueryParam qp l =
|
||||
l { _queryParams = _queryParams l <> [qp] }
|
||||
|
||||
-- | Transform 'Link' into 'URI'.
|
||||
--
|
||||
-- >>> type API = "something" :> Get '[JSON] Int
|
||||
-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
|
||||
-- something
|
||||
--
|
||||
-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
|
||||
-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
|
||||
-- sum?x[]=1&x[]=2&x[]=3
|
||||
--
|
||||
-- >>> type API = "foo/bar" :> Get '[JSON] Int
|
||||
-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
|
||||
-- foo%2Fbar
|
||||
--
|
||||
-- >>> type SomeRoute = "abc" :> Capture "email" String :> Put '[JSON] ()
|
||||
-- >>> let someRoute = Proxy :: Proxy SomeRoute
|
||||
-- >>> safeLink someRoute someRoute "test@example.com"
|
||||
-- Link {_segments = ["abc","test%40example.com"], _queryParams = []}
|
||||
--
|
||||
-- >>> linkURI $ safeLink someRoute someRoute "test@example.com"
|
||||
-- abc/test%40example.com
|
||||
--
|
||||
linkURI :: Link -> URI
|
||||
linkURI = linkURI' LinkArrayElementBracket
|
||||
|
||||
-- | How to encode array query elements.
|
||||
data LinkArrayElementStyle
|
||||
= LinkArrayElementBracket -- ^ @foo[]=1&foo[]=2@
|
||||
| LinkArrayElementPlain -- ^ @foo=1&foo=2@
|
||||
deriving (Eq, Ord, Show, Enum, Bounded)
|
||||
|
||||
-- | Configurable 'linkURI'.
|
||||
--
|
||||
-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
|
||||
-- >>> linkURI' LinkArrayElementBracket $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
|
||||
-- sum?x[]=1&x[]=2&x[]=3
|
||||
--
|
||||
-- >>> linkURI' LinkArrayElementPlain $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
|
||||
-- sum?x=1&x=2&x=3
|
||||
--
|
||||
linkURI' :: LinkArrayElementStyle -> Link -> URI
|
||||
linkURI' addBrackets (Link segments q_params) =
|
||||
URI mempty -- No scheme (relative)
|
||||
Nothing -- Or authority (relative)
|
||||
(intercalate "/" $ map getEscaped segments)
|
||||
(makeQueries q_params) mempty
|
||||
where
|
||||
makeQueries :: [Param] -> String
|
||||
makeQueries [] = ""
|
||||
makeQueries xs =
|
||||
"?" <> intercalate "&" (fmap makeQuery xs)
|
||||
|
||||
makeQuery :: Param -> String
|
||||
makeQuery (ArrayElemParam k v) = escape k <> style <> escape (Text.unpack v)
|
||||
makeQuery (SingleParam k v) = escape k <> "=" <> escape (Text.unpack v)
|
||||
makeQuery (FlagParam k) = escape k
|
||||
|
||||
style = case addBrackets of
|
||||
LinkArrayElementBracket -> "[]="
|
||||
LinkArrayElementPlain -> "="
|
||||
|
||||
escape :: String -> String
|
||||
escape = escapeURIString isUnreserved
|
||||
|
||||
-- | Create a valid (by construction) relative URI with query params.
|
||||
--
|
||||
-- This function will only typecheck if `endpoint` is part of the API `api`
|
||||
safeLink
|
||||
:: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
|
||||
=> Proxy api -- ^ The whole API that this endpoint is a part of
|
||||
-> Proxy endpoint -- ^ The API endpoint you would like to point to
|
||||
-> MkLink endpoint Link
|
||||
safeLink = safeLink' id
|
||||
|
||||
-- | More general 'safeLink'.
|
||||
--
|
||||
safeLink'
|
||||
:: forall endpoint api a. (IsElem endpoint api, HasLink endpoint)
|
||||
=> (Link -> a)
|
||||
-> Proxy api -- ^ The whole API that this endpoint is a part of
|
||||
-> Proxy endpoint -- ^ The API endpoint you would like to point to
|
||||
-> MkLink endpoint a
|
||||
safeLink' toA _ endpoint = toLink toA endpoint (Link mempty mempty)
|
||||
|
||||
-- | Create all links in an API.
|
||||
--
|
||||
-- Note that the @api@ type must be restricted to the endpoints that have
|
||||
-- valid links to them.
|
||||
--
|
||||
-- >>> type API = "foo" :> Capture "name" Text :> Get '[JSON] Text :<|> "bar" :> Capture "name" Int :> Get '[JSON] Double
|
||||
-- >>> let fooLink :<|> barLink = allLinks (Proxy :: Proxy API)
|
||||
-- >>> :t fooLink
|
||||
-- fooLink :: Text -> Link
|
||||
-- >>> :t barLink
|
||||
-- barLink :: Int -> Link
|
||||
--
|
||||
-- Note: nested APIs don't work well with this approach
|
||||
--
|
||||
-- >>> :kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link
|
||||
-- MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link :: *
|
||||
-- = Char -> (Int -> Link) :<|> (Double -> Link)
|
||||
allLinks
|
||||
:: forall api. HasLink api
|
||||
=> Proxy api
|
||||
-> MkLink api Link
|
||||
allLinks = allLinks' id
|
||||
|
||||
-- | More general 'allLinks'. See `safeLink'`.
|
||||
allLinks'
|
||||
:: forall api a. HasLink api
|
||||
=> (Link -> a)
|
||||
-> Proxy api
|
||||
-> MkLink api a
|
||||
allLinks' toA api = toLink toA api (Link mempty mempty)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Generics
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- | Given an API record field, create a link for that route. Only the field's
|
||||
-- type is used.
|
||||
--
|
||||
-- @
|
||||
-- data Record route = Record
|
||||
-- { _get :: route :- Capture "id" Int :> Get '[JSON] String
|
||||
-- , _put :: route :- ReqBody '[JSON] Int :> Put '[JSON] Bool
|
||||
-- }
|
||||
-- deriving ('Generic')
|
||||
--
|
||||
-- getLink :: Int -> Link
|
||||
-- getLink = 'fieldLink' _get
|
||||
-- @
|
||||
--
|
||||
-- @since 0.14.1
|
||||
fieldLink
|
||||
:: ( IsElem endpoint (ToServantApi routes), HasLink endpoint
|
||||
, GenericServant routes AsApi
|
||||
)
|
||||
=> (routes AsApi -> endpoint)
|
||||
-> MkLink endpoint Link
|
||||
fieldLink = fieldLink' id
|
||||
|
||||
-- | More general version of 'fieldLink'
|
||||
--
|
||||
-- @since 0.14.1
|
||||
fieldLink'
|
||||
:: forall routes endpoint a.
|
||||
( IsElem endpoint (ToServantApi routes), HasLink endpoint
|
||||
, GenericServant routes AsApi
|
||||
)
|
||||
=> (Link -> a)
|
||||
-> (routes AsApi -> endpoint)
|
||||
-> MkLink endpoint a
|
||||
fieldLink' toA _ = safeLink' toA (genericApi (Proxy :: Proxy routes)) (Proxy :: Proxy endpoint)
|
||||
|
||||
-- | A type that specifies that an API record contains a set of links.
|
||||
--
|
||||
-- @since 0.14.1
|
||||
data AsLink (a :: *)
|
||||
instance GenericMode (AsLink a) where
|
||||
type (AsLink a) :- api = MkLink api a
|
||||
|
||||
-- | Get all links as a record.
|
||||
--
|
||||
-- @since 0.14.1
|
||||
allFieldLinks
|
||||
:: ( HasLink (ToServantApi routes)
|
||||
, GenericServant routes (AsLink Link)
|
||||
, ToServant routes (AsLink Link) ~ MkLink (ToServantApi routes) Link
|
||||
)
|
||||
=> routes (AsLink Link)
|
||||
allFieldLinks = allFieldLinks' id
|
||||
|
||||
-- | More general version of 'allFieldLinks'.
|
||||
--
|
||||
-- @since 0.14.1
|
||||
allFieldLinks'
|
||||
:: forall routes a.
|
||||
( HasLink (ToServantApi routes)
|
||||
, GenericServant routes (AsLink a)
|
||||
, ToServant routes (AsLink a) ~ MkLink (ToServantApi routes) a
|
||||
)
|
||||
=> (Link -> a)
|
||||
-> routes (AsLink a)
|
||||
allFieldLinks' toA
|
||||
= fromServant
|
||||
$ allLinks' toA (Proxy :: Proxy (ToServantApi routes))
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- HasLink
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- | Construct a toLink for an endpoint.
|
||||
class HasLink endpoint where
|
||||
type MkLink endpoint (a :: *)
|
||||
toLink
|
||||
:: (Link -> a)
|
||||
-> Proxy endpoint -- ^ The API endpoint you would like to point to
|
||||
-> Link
|
||||
-> MkLink endpoint a
|
||||
|
||||
-- Naked symbol instance
|
||||
instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
|
||||
type MkLink (sym :> sub) a = MkLink sub a
|
||||
toLink toA _ =
|
||||
toLink toA (Proxy :: Proxy sub) . addSegment (escaped seg)
|
||||
where
|
||||
seg = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
-- QueryParam instances
|
||||
instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mods))
|
||||
=> HasLink (QueryParam' mods sym v :> sub)
|
||||
where
|
||||
type MkLink (QueryParam' mods sym v :> sub) a = If (FoldRequired mods) v (Maybe v) -> MkLink sub a
|
||||
toLink toA _ l mv =
|
||||
toLink toA (Proxy :: Proxy sub) $
|
||||
case sbool :: SBool (FoldRequired mods) of
|
||||
STrue -> (addQueryParam . SingleParam k . toQueryParam) mv l
|
||||
SFalse -> maybe id (addQueryParam . SingleParam k . toQueryParam) mv l
|
||||
where
|
||||
k :: String
|
||||
k = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
|
||||
=> HasLink (QueryParams sym v :> sub)
|
||||
where
|
||||
type MkLink (QueryParams sym v :> sub) a = [v] -> MkLink sub a
|
||||
toLink toA _ l =
|
||||
toLink toA (Proxy :: Proxy sub) .
|
||||
foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l
|
||||
where
|
||||
k = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
instance (KnownSymbol sym, HasLink sub)
|
||||
=> HasLink (QueryFlag sym :> sub)
|
||||
where
|
||||
type MkLink (QueryFlag sym :> sub) a = Bool -> MkLink sub a
|
||||
toLink toA _ l False =
|
||||
toLink toA (Proxy :: Proxy sub) l
|
||||
toLink toA _ l True =
|
||||
toLink toA (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
|
||||
where
|
||||
k = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
-- :<|> instance - Generate all links at once
|
||||
instance (HasLink a, HasLink b) => HasLink (a :<|> b) where
|
||||
type MkLink (a :<|> b) r = MkLink a r :<|> MkLink b r
|
||||
toLink toA _ l = toLink toA (Proxy :: Proxy a) l :<|> toLink toA (Proxy :: Proxy b) l
|
||||
|
||||
-- Misc instances
|
||||
instance HasLink sub => HasLink (ReqBody' mods ct a :> sub) where
|
||||
type MkLink (ReqBody' mods ct a :> sub) r = MkLink sub r
|
||||
toLink toA _ = toLink toA (Proxy :: Proxy sub)
|
||||
|
||||
instance (ToHttpApiData v, HasLink sub)
|
||||
=> HasLink (Capture' mods sym v :> sub)
|
||||
where
|
||||
type MkLink (Capture' mods sym v :> sub) a = v -> MkLink sub a
|
||||
toLink toA _ l v =
|
||||
toLink toA (Proxy :: Proxy sub) $
|
||||
addSegment (escaped . Text.unpack $ toUrlPiece v) l
|
||||
|
||||
instance (ToHttpApiData v, HasLink sub)
|
||||
=> HasLink (CaptureAll sym v :> sub)
|
||||
where
|
||||
type MkLink (CaptureAll sym v :> sub) a = [v] -> MkLink sub a
|
||||
toLink toA _ l vs = toLink toA (Proxy :: Proxy sub) $
|
||||
foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
|
||||
|
||||
instance HasLink sub => HasLink (Header' mods sym (a :: *) :> sub) where
|
||||
type MkLink (Header' mods sym a :> sub) r = MkLink sub r
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (Vault :> sub) where
|
||||
type MkLink (Vault :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (Description s :> sub) where
|
||||
type MkLink (Description s :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (Summary s :> sub) where
|
||||
type MkLink (Summary s :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (HttpVersion :> sub) where
|
||||
type MkLink (HttpVersion:> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (IsSecure :> sub) where
|
||||
type MkLink (IsSecure :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (WithNamedContext name context sub) where
|
||||
type MkLink (WithNamedContext name context sub) a = MkLink sub a
|
||||
toLink toA _ = toLink toA (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (RemoteHost :> sub) where
|
||||
type MkLink (RemoteHost :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
|
||||
type MkLink (BasicAuth realm a :> sub) r = MkLink sub r
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink EmptyAPI where
|
||||
type MkLink EmptyAPI a = EmptyAPI
|
||||
toLink _ _ _ = EmptyAPI
|
||||
|
||||
-- Verb (terminal) instances
|
||||
instance HasLink (Verb m s ct a) where
|
||||
type MkLink (Verb m s ct a) r = r
|
||||
toLink toA _ = toA
|
||||
|
||||
instance HasLink Raw where
|
||||
type MkLink Raw a = a
|
||||
toLink toA _ = toA
|
||||
|
||||
instance HasLink (Stream m status fr ct a) where
|
||||
type MkLink (Stream m status fr ct a) r = r
|
||||
toLink toA _ = toA
|
||||
|
||||
-- AuthProtext instances
|
||||
instance HasLink sub => HasLink (AuthProtect tag :> sub) where
|
||||
type MkLink (AuthProtect tag :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
-- | Helper for implemneting 'toLink' for combinators not affecting link
|
||||
-- structure.
|
||||
simpleToLink
|
||||
:: forall sub a combinator.
|
||||
(HasLink sub, MkLink sub a ~ MkLink (combinator :> sub) a)
|
||||
=> Proxy sub
|
||||
-> (Link -> a)
|
||||
-> Proxy (combinator :> sub)
|
||||
-> Link
|
||||
-> MkLink (combinator :> sub) a
|
||||
simpleToLink _ toA _ = toLink toA (Proxy :: Proxy sub)
|
||||
|
||||
|
||||
-- $setup
|
||||
-- >>> import Servant.API
|
||||
-- >>> import Data.Text (Text)
|
|
@ -1,487 +1,6 @@
|
|||
{-# LANGUAGE ConstraintKinds #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE PolyKinds #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
{-# OPTIONS_HADDOCK not-home #-}
|
||||
|
||||
-- | Type safe generation of internal links.
|
||||
--
|
||||
-- Given an API with a few endpoints:
|
||||
--
|
||||
-- >>> :set -XDataKinds -XTypeFamilies -XTypeOperators
|
||||
-- >>> import Servant.API
|
||||
-- >>> import Servant.Utils.Links
|
||||
-- >>> import Data.Proxy
|
||||
-- >>>
|
||||
-- >>> type Hello = "hello" :> Get '[JSON] Int
|
||||
-- >>> type Bye = "bye" :> QueryParam "name" String :> Delete '[JSON] NoContent
|
||||
-- >>> type API = Hello :<|> Bye
|
||||
-- >>> let api = Proxy :: Proxy API
|
||||
--
|
||||
-- It is possible to generate links that are guaranteed to be within 'API' with
|
||||
-- 'safeLink'. The first argument to 'safeLink' is a type representing the API
|
||||
-- you would like to restrict links to. The second argument is the destination
|
||||
-- endpoint you would like the link to point to, this will need to end with a
|
||||
-- verb like GET or POST. Further arguments may be required depending on the
|
||||
-- type of the endpoint. If everything lines up you will get a 'Link' out the
|
||||
-- other end.
|
||||
--
|
||||
-- You may omit 'QueryParam's and the like should you not want to provide them,
|
||||
-- but types which form part of the URL path like 'Capture' must be included.
|
||||
-- The reason you may want to omit 'QueryParam's is that safeLink is a bit
|
||||
-- magical: if parameters are included that could take input it will return a
|
||||
-- function that accepts that input and generates a link. This is best shown
|
||||
-- with an example. Here, a link is generated with no parameters:
|
||||
--
|
||||
-- >>> let hello = Proxy :: Proxy ("hello" :> Get '[JSON] Int)
|
||||
-- >>> toUrlPiece (safeLink api hello :: Link)
|
||||
-- "hello"
|
||||
--
|
||||
-- If the API has an endpoint with parameters then we can generate links with
|
||||
-- or without those:
|
||||
--
|
||||
-- >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete '[JSON] NoContent)
|
||||
-- >>> toUrlPiece $ safeLink api with (Just "Hubert")
|
||||
-- "bye?name=Hubert"
|
||||
--
|
||||
-- >>> let without = Proxy :: Proxy ("bye" :> Delete '[JSON] NoContent)
|
||||
-- >>> toUrlPiece $ safeLink api without
|
||||
-- "bye"
|
||||
--
|
||||
-- If you would like create a helper for generating links only within that API,
|
||||
-- you can partially apply safeLink if you specify a correct type signature
|
||||
-- like so:
|
||||
--
|
||||
-- >>> :set -XConstraintKinds
|
||||
-- >>> :{
|
||||
-- >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
|
||||
-- >>> => Proxy endpoint -> MkLink endpoint Link
|
||||
-- >>> apiLink = safeLink api
|
||||
-- >>> :}
|
||||
--
|
||||
-- `safeLink'` allows to make specialise the output:
|
||||
--
|
||||
-- >>> safeLink' toUrlPiece api without
|
||||
-- "bye"
|
||||
--
|
||||
-- >>> :{
|
||||
-- >>> let apiTextLink :: (IsElem endpoint API, HasLink endpoint)
|
||||
-- >>> => Proxy endpoint -> MkLink endpoint Text
|
||||
-- >>> apiTextLink = safeLink' toUrlPiece api
|
||||
-- >>> :}
|
||||
--
|
||||
-- >>> apiTextLink without
|
||||
-- "bye"
|
||||
--
|
||||
-- Attempting to construct a link to an endpoint that does not exist in api
|
||||
-- will result in a type error like this:
|
||||
--
|
||||
-- >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] NoContent)
|
||||
-- >>> safeLink api bad_link
|
||||
-- ...
|
||||
-- ...Could not deduce...
|
||||
-- ...
|
||||
--
|
||||
-- This error is essentially saying that the type family couldn't find
|
||||
-- bad_link under api after trying the open (but empty) type family
|
||||
-- `IsElem'` as a last resort.
|
||||
module Servant.Utils.Links (
|
||||
module Servant.API.TypeLevel,
|
||||
|
||||
-- * Building and using safe links
|
||||
--
|
||||
-- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
|
||||
safeLink
|
||||
, safeLink'
|
||||
, allLinks
|
||||
, allLinks'
|
||||
, URI(..)
|
||||
-- * Adding custom types
|
||||
, HasLink(..)
|
||||
, Link
|
||||
, linkURI
|
||||
, linkURI'
|
||||
, LinkArrayElementStyle (..)
|
||||
-- ** Link accessors
|
||||
, Param (..)
|
||||
, linkSegments
|
||||
, linkQueryParams
|
||||
) where
|
||||
|
||||
import Data.List
|
||||
import Data.Proxy
|
||||
(Proxy (..))
|
||||
import Data.Semigroup
|
||||
((<>))
|
||||
import Data.Singletons.Bool
|
||||
(SBool (..), SBoolI (..))
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import Data.Type.Bool
|
||||
(If)
|
||||
import GHC.TypeLits
|
||||
(KnownSymbol, symbolVal)
|
||||
import Network.URI
|
||||
(URI (..), escapeURIString, isUnreserved)
|
||||
import Prelude ()
|
||||
import Prelude.Compat
|
||||
|
||||
import Servant.API.Alternative
|
||||
((:<|>) ((:<|>)))
|
||||
import Servant.API.BasicAuth
|
||||
(BasicAuth)
|
||||
import Servant.API.Capture
|
||||
(Capture', CaptureAll)
|
||||
import Servant.API.Description
|
||||
(Description, Summary)
|
||||
import Servant.API.Empty
|
||||
(EmptyAPI (..))
|
||||
import Servant.API.Experimental.Auth
|
||||
(AuthProtect)
|
||||
import Servant.API.Header
|
||||
(Header')
|
||||
import Servant.API.HttpVersion
|
||||
(HttpVersion)
|
||||
import Servant.API.IsSecure
|
||||
(IsSecure)
|
||||
import Servant.API.Modifiers
|
||||
(FoldRequired)
|
||||
import Servant.API.QueryParam
|
||||
(QueryFlag, QueryParam', QueryParams)
|
||||
import Servant.API.Raw
|
||||
(Raw)
|
||||
import Servant.API.RemoteHost
|
||||
(RemoteHost)
|
||||
import Servant.API.ReqBody
|
||||
(ReqBody')
|
||||
import Servant.API.Stream
|
||||
(Stream)
|
||||
import Servant.API.Sub
|
||||
(type (:>))
|
||||
import Servant.API.TypeLevel
|
||||
import Servant.API.Vault
|
||||
(Vault)
|
||||
import Servant.API.Verbs
|
||||
(Verb)
|
||||
import Servant.API.WithNamedContext
|
||||
(WithNamedContext)
|
||||
import Web.HttpApiData
|
||||
|
||||
-- | A safe link datatype.
|
||||
-- The only way of constructing a 'Link' is using 'safeLink', which means any
|
||||
-- 'Link' is guaranteed to be part of the mentioned API.
|
||||
data Link = Link
|
||||
{ _segments :: [Escaped]
|
||||
, _queryParams :: [Param]
|
||||
} deriving Show
|
||||
|
||||
newtype Escaped = Escaped String
|
||||
|
||||
escaped :: String -> Escaped
|
||||
escaped = Escaped . escapeURIString isUnreserved
|
||||
|
||||
getEscaped :: Escaped -> String
|
||||
getEscaped (Escaped s) = s
|
||||
|
||||
instance Show Escaped where
|
||||
showsPrec d (Escaped s) = showsPrec d s
|
||||
show (Escaped s) = show s
|
||||
|
||||
linkSegments :: Link -> [String]
|
||||
linkSegments = map getEscaped . _segments
|
||||
|
||||
linkQueryParams :: Link -> [Param]
|
||||
linkQueryParams = _queryParams
|
||||
|
||||
instance ToHttpApiData Link where
|
||||
toHeader = TE.encodeUtf8 . toUrlPiece
|
||||
toUrlPiece l =
|
||||
let uri = linkURI l
|
||||
in Text.pack $ uriPath uri ++ uriQuery uri
|
||||
|
||||
-- | Query parameter.
|
||||
data Param
|
||||
= SingleParam String Text.Text
|
||||
| ArrayElemParam String Text.Text
|
||||
| FlagParam String
|
||||
deriving Show
|
||||
|
||||
addSegment :: Escaped -> Link -> Link
|
||||
addSegment seg l = l { _segments = _segments l <> [seg] }
|
||||
|
||||
addQueryParam :: Param -> Link -> Link
|
||||
addQueryParam qp l =
|
||||
l { _queryParams = _queryParams l <> [qp] }
|
||||
|
||||
-- | Transform 'Link' into 'URI'.
|
||||
--
|
||||
-- >>> type API = "something" :> Get '[JSON] Int
|
||||
-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
|
||||
-- something
|
||||
--
|
||||
-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
|
||||
-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
|
||||
-- sum?x[]=1&x[]=2&x[]=3
|
||||
--
|
||||
-- >>> type API = "foo/bar" :> Get '[JSON] Int
|
||||
-- >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
|
||||
-- foo%2Fbar
|
||||
--
|
||||
-- >>> type SomeRoute = "abc" :> Capture "email" String :> Put '[JSON] ()
|
||||
-- >>> let someRoute = Proxy :: Proxy SomeRoute
|
||||
-- >>> safeLink someRoute someRoute "test@example.com"
|
||||
-- Link {_segments = ["abc","test%40example.com"], _queryParams = []}
|
||||
--
|
||||
-- >>> linkURI $ safeLink someRoute someRoute "test@example.com"
|
||||
-- abc/test%40example.com
|
||||
--
|
||||
linkURI :: Link -> URI
|
||||
linkURI = linkURI' LinkArrayElementBracket
|
||||
|
||||
-- | How to encode array query elements.
|
||||
data LinkArrayElementStyle
|
||||
= LinkArrayElementBracket -- ^ @foo[]=1&foo[]=2@
|
||||
| LinkArrayElementPlain -- ^ @foo=1&foo=2@
|
||||
deriving (Eq, Ord, Show, Enum, Bounded)
|
||||
|
||||
-- | Configurable 'linkURI'.
|
||||
--
|
||||
-- >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
|
||||
-- >>> linkURI' LinkArrayElementBracket $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
|
||||
-- sum?x[]=1&x[]=2&x[]=3
|
||||
--
|
||||
-- >>> linkURI' LinkArrayElementPlain $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
|
||||
-- sum?x=1&x=2&x=3
|
||||
--
|
||||
linkURI' :: LinkArrayElementStyle -> Link -> URI
|
||||
linkURI' addBrackets (Link segments q_params) =
|
||||
URI mempty -- No scheme (relative)
|
||||
Nothing -- Or authority (relative)
|
||||
(intercalate "/" $ map getEscaped segments)
|
||||
(makeQueries q_params) mempty
|
||||
module Servant.Utils.Links
|
||||
{-# DEPRECATED "Use Servant.Links." #-}
|
||||
( module Servant.Links )
|
||||
where
|
||||
makeQueries :: [Param] -> String
|
||||
makeQueries [] = ""
|
||||
makeQueries xs =
|
||||
"?" <> intercalate "&" (fmap makeQuery xs)
|
||||
|
||||
makeQuery :: Param -> String
|
||||
makeQuery (ArrayElemParam k v) = escape k <> style <> escape (Text.unpack v)
|
||||
makeQuery (SingleParam k v) = escape k <> "=" <> escape (Text.unpack v)
|
||||
makeQuery (FlagParam k) = escape k
|
||||
|
||||
style = case addBrackets of
|
||||
LinkArrayElementBracket -> "[]="
|
||||
LinkArrayElementPlain -> "="
|
||||
|
||||
escape :: String -> String
|
||||
escape = escapeURIString isUnreserved
|
||||
|
||||
-- | Create a valid (by construction) relative URI with query params.
|
||||
--
|
||||
-- This function will only typecheck if `endpoint` is part of the API `api`
|
||||
safeLink
|
||||
:: forall endpoint api. (IsElem endpoint api, HasLink endpoint)
|
||||
=> Proxy api -- ^ The whole API that this endpoint is a part of
|
||||
-> Proxy endpoint -- ^ The API endpoint you would like to point to
|
||||
-> MkLink endpoint Link
|
||||
safeLink = safeLink' id
|
||||
|
||||
-- | More general 'safeLink'.
|
||||
--
|
||||
safeLink'
|
||||
:: forall endpoint api a. (IsElem endpoint api, HasLink endpoint)
|
||||
=> (Link -> a)
|
||||
-> Proxy api -- ^ The whole API that this endpoint is a part of
|
||||
-> Proxy endpoint -- ^ The API endpoint you would like to point to
|
||||
-> MkLink endpoint a
|
||||
safeLink' toA _ endpoint = toLink toA endpoint (Link mempty mempty)
|
||||
|
||||
-- | Create all links in an API.
|
||||
--
|
||||
-- Note that the @api@ type must be restricted to the endpoints that have
|
||||
-- valid links to them.
|
||||
--
|
||||
-- >>> type API = "foo" :> Capture "name" Text :> Get '[JSON] Text :<|> "bar" :> Capture "name" Int :> Get '[JSON] Double
|
||||
-- >>> let fooLink :<|> barLink = allLinks (Proxy :: Proxy API)
|
||||
-- >>> :t fooLink
|
||||
-- fooLink :: Text -> Link
|
||||
-- >>> :t barLink
|
||||
-- barLink :: Int -> Link
|
||||
--
|
||||
-- Note: nested APIs don't work well with this approach
|
||||
--
|
||||
-- >>> :kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link
|
||||
-- MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) Link :: *
|
||||
-- = Char -> (Int -> Link) :<|> (Double -> Link)
|
||||
allLinks
|
||||
:: forall api. HasLink api
|
||||
=> Proxy api
|
||||
-> MkLink api Link
|
||||
allLinks = allLinks' id
|
||||
|
||||
-- | More general 'allLinks'. See `safeLink'`.
|
||||
allLinks'
|
||||
:: forall api a. HasLink api
|
||||
=> (Link -> a)
|
||||
-> Proxy api
|
||||
-> MkLink api a
|
||||
allLinks' toA api = toLink toA api (Link mempty mempty)
|
||||
|
||||
-- | Construct a toLink for an endpoint.
|
||||
class HasLink endpoint where
|
||||
type MkLink endpoint (a :: *)
|
||||
toLink
|
||||
:: (Link -> a)
|
||||
-> Proxy endpoint -- ^ The API endpoint you would like to point to
|
||||
-> Link
|
||||
-> MkLink endpoint a
|
||||
|
||||
-- Naked symbol instance
|
||||
instance (KnownSymbol sym, HasLink sub) => HasLink (sym :> sub) where
|
||||
type MkLink (sym :> sub) a = MkLink sub a
|
||||
toLink toA _ =
|
||||
toLink toA (Proxy :: Proxy sub) . addSegment (escaped seg)
|
||||
where
|
||||
seg = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
-- QueryParam instances
|
||||
instance (KnownSymbol sym, ToHttpApiData v, HasLink sub, SBoolI (FoldRequired mods))
|
||||
=> HasLink (QueryParam' mods sym v :> sub)
|
||||
where
|
||||
type MkLink (QueryParam' mods sym v :> sub) a = If (FoldRequired mods) v (Maybe v) -> MkLink sub a
|
||||
toLink toA _ l mv =
|
||||
toLink toA (Proxy :: Proxy sub) $
|
||||
case sbool :: SBool (FoldRequired mods) of
|
||||
STrue -> (addQueryParam . SingleParam k . toQueryParam) mv l
|
||||
SFalse -> maybe id (addQueryParam . SingleParam k . toQueryParam) mv l
|
||||
where
|
||||
k :: String
|
||||
k = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
|
||||
=> HasLink (QueryParams sym v :> sub)
|
||||
where
|
||||
type MkLink (QueryParams sym v :> sub) a = [v] -> MkLink sub a
|
||||
toLink toA _ l =
|
||||
toLink toA (Proxy :: Proxy sub) .
|
||||
foldl' (\l' v -> addQueryParam (ArrayElemParam k (toQueryParam v)) l') l
|
||||
where
|
||||
k = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
instance (KnownSymbol sym, HasLink sub)
|
||||
=> HasLink (QueryFlag sym :> sub)
|
||||
where
|
||||
type MkLink (QueryFlag sym :> sub) a = Bool -> MkLink sub a
|
||||
toLink toA _ l False =
|
||||
toLink toA (Proxy :: Proxy sub) l
|
||||
toLink toA _ l True =
|
||||
toLink toA (Proxy :: Proxy sub) $ addQueryParam (FlagParam k) l
|
||||
where
|
||||
k = symbolVal (Proxy :: Proxy sym)
|
||||
|
||||
-- :<|> instance - Generate all links at once
|
||||
instance (HasLink a, HasLink b) => HasLink (a :<|> b) where
|
||||
type MkLink (a :<|> b) r = MkLink a r :<|> MkLink b r
|
||||
toLink toA _ l = toLink toA (Proxy :: Proxy a) l :<|> toLink toA (Proxy :: Proxy b) l
|
||||
|
||||
-- Misc instances
|
||||
instance HasLink sub => HasLink (ReqBody' mods ct a :> sub) where
|
||||
type MkLink (ReqBody' mods ct a :> sub) r = MkLink sub r
|
||||
toLink toA _ = toLink toA (Proxy :: Proxy sub)
|
||||
|
||||
instance (ToHttpApiData v, HasLink sub)
|
||||
=> HasLink (Capture' mods sym v :> sub)
|
||||
where
|
||||
type MkLink (Capture' mods sym v :> sub) a = v -> MkLink sub a
|
||||
toLink toA _ l v =
|
||||
toLink toA (Proxy :: Proxy sub) $
|
||||
addSegment (escaped . Text.unpack $ toUrlPiece v) l
|
||||
|
||||
instance (ToHttpApiData v, HasLink sub)
|
||||
=> HasLink (CaptureAll sym v :> sub)
|
||||
where
|
||||
type MkLink (CaptureAll sym v :> sub) a = [v] -> MkLink sub a
|
||||
toLink toA _ l vs = toLink toA (Proxy :: Proxy sub) $
|
||||
foldl' (flip $ addSegment . escaped . Text.unpack . toUrlPiece) l vs
|
||||
|
||||
instance HasLink sub => HasLink (Header' mods sym (a :: *) :> sub) where
|
||||
type MkLink (Header' mods sym a :> sub) r = MkLink sub r
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (Vault :> sub) where
|
||||
type MkLink (Vault :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (Description s :> sub) where
|
||||
type MkLink (Description s :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (Summary s :> sub) where
|
||||
type MkLink (Summary s :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (HttpVersion :> sub) where
|
||||
type MkLink (HttpVersion:> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (IsSecure :> sub) where
|
||||
type MkLink (IsSecure :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (WithNamedContext name context sub) where
|
||||
type MkLink (WithNamedContext name context sub) a = MkLink sub a
|
||||
toLink toA _ = toLink toA (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (RemoteHost :> sub) where
|
||||
type MkLink (RemoteHost :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink sub => HasLink (BasicAuth realm a :> sub) where
|
||||
type MkLink (BasicAuth realm a :> sub) r = MkLink sub r
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
instance HasLink EmptyAPI where
|
||||
type MkLink EmptyAPI a = EmptyAPI
|
||||
toLink _ _ _ = EmptyAPI
|
||||
|
||||
-- Verb (terminal) instances
|
||||
instance HasLink (Verb m s ct a) where
|
||||
type MkLink (Verb m s ct a) r = r
|
||||
toLink toA _ = toA
|
||||
|
||||
instance HasLink Raw where
|
||||
type MkLink Raw a = a
|
||||
toLink toA _ = toA
|
||||
|
||||
instance HasLink (Stream m fr ct a) where
|
||||
type MkLink (Stream m fr ct a) r = r
|
||||
toLink toA _ = toA
|
||||
|
||||
-- AuthProtext instances
|
||||
instance HasLink sub => HasLink (AuthProtect tag :> sub) where
|
||||
type MkLink (AuthProtect tag :> sub) a = MkLink sub a
|
||||
toLink = simpleToLink (Proxy :: Proxy sub)
|
||||
|
||||
-- | Helper for implemneting 'toLink' for combinators not affecting link
|
||||
-- structure.
|
||||
simpleToLink
|
||||
:: forall sub a combinator.
|
||||
(HasLink sub, MkLink sub a ~ MkLink (combinator :> sub) a)
|
||||
=> Proxy sub
|
||||
-> (Link -> a)
|
||||
-> Proxy (combinator :> sub)
|
||||
-> Link
|
||||
-> MkLink (combinator :> sub) a
|
||||
simpleToLink _ toA _ = toLink toA (Proxy :: Proxy sub)
|
||||
|
||||
|
||||
-- $setup
|
||||
-- >>> import Servant.API
|
||||
-- >>> import Data.Text (Text)
|
||||
import Servant.Links
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#if __GLASGOW_HASKELL__ < 709
|
||||
{-# OPTIONS_GHC -fcontext-stack=41 #-}
|
||||
#endif
|
||||
module Servant.Utils.LinksSpec where
|
||||
module Servant.LinksSpec where
|
||||
|
||||
import Data.Proxy (Proxy (..))
|
||||
import Test.Hspec (Expectation, Spec, describe, it,
|
||||
|
@ -15,7 +15,7 @@ import Test.Hspec (Expectation, Spec, describe, it,
|
|||
import Data.String (fromString)
|
||||
|
||||
import Servant.API
|
||||
import Servant.Utils.Links
|
||||
import Servant.Links
|
||||
import Servant.API.Internal.Test.ComprehensiveAPI (comprehensiveAPIWithoutRaw)
|
||||
|
||||
type TestApi =
|
||||
|
@ -51,7 +51,7 @@ shouldBeLink link expected =
|
|||
toUrlPiece link `shouldBe` fromString expected
|
||||
|
||||
spec :: Spec
|
||||
spec = describe "Servant.Utils.Links" $ do
|
||||
spec = describe "Servant.Links" $ do
|
||||
it "generates correct links for capture query params" $ do
|
||||
let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete '[JSON] NoContent)
|
||||
apiLink l1 "hi" `shouldBeLink` "hello/hi"
|
|
@ -0,0 +1,11 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(name='servant-rtd',
|
||||
version='0.0.1',
|
||||
description='Documentation for the haskell-servant platform',
|
||||
url='https://github.com/haskell-servant/servant',
|
||||
author='Servant Contributors',
|
||||
author_email='haskell-servant-maintainers@googlegroups.com',
|
||||
license='BSD3',
|
||||
zip_safe=False)
|
||||
|
15
stack.yaml
15
stack.yaml
|
@ -1,5 +1,5 @@
|
|||
# Let's try to keep resolver at the first day of the month
|
||||
resolver: nightly-2018-03-01
|
||||
resolver: nightly-2018-06-01
|
||||
packages:
|
||||
- servant-client/
|
||||
- servant-client-core/
|
||||
|
@ -8,18 +8,5 @@ packages:
|
|||
- servant-server/
|
||||
- servant/
|
||||
|
||||
extra-deps:
|
||||
- cabal-doctest-1.0.6
|
||||
- http-api-data-0.3.7.2
|
||||
- http-types-0.12
|
||||
- text-1.2.3.0
|
||||
- aeson-1.3.0.0
|
||||
- exceptions-0.10.0
|
||||
- aeson-compat-0.3.7.1
|
||||
- free-5.0.1
|
||||
- lens-4.16
|
||||
- random-bytestring-0.1.3
|
||||
- pcg-random-0.1.3.5
|
||||
|
||||
# allow-newer: true # ignores all bounds, that's a sledgehammer
|
||||
# - doc/tutorial/
|
||||
|
|
Loading…
Reference in New Issue