git.iko.soy/typeable/servant
on master help sign in
Merge pull request #355 from haskell-servant/tutorial

Tutorial

commiteb11fe6b02e796b411a994df2e879c50457bdfea authorJulian Arni <jkarni@gmail.com> dateSat 19 Mar 2016, 12:04 (11 years ago) parent81ef373610b837
50 files changed+2828−1767
.gitignore+4 doc/Makefile+216 doc/building-the-docs+8 doc/conf.py+294 doc/index.rst+22 doc/introduction.rst+40 doc/links.rst+34 doc/requirements.txt+25 doc/tutorial/.ghci+1 doc/tutorial/ApiType.lhs+311 doc/tutorial/Client.lhs+156 doc/tutorial/Docs.lhs+234 doc/tutorial/Javascript.lhs+228 servant-examples/LICENSE → doc/tutorial/LICENSE+2−2 doc/tutorial/Server.lhs+1115 doc/tutorial/Setup.hs+2 doc/tutorial/index.rst+18 servant-examples/tutorial/t9/index.html → doc/tutorial/static/index.html+3−3 servant-examples/tutorial/t9/ui.js → doc/tutorial/static/ui.js−1 doc/tutorial/test/JavascriptSpec.hs+32 doc/tutorial/test/Spec.hs+1 doc/tutorial/tinc.yaml+13 doc/tutorial/tutorial.cabal+63 servant-examples/Setup.hs−2 servant-examples/auth-combinator/auth-combinator.hs−124 servant-examples/basic-auth/basic-auth.hs−105 servant-examples/hackage/hackage.hs−90 servant-examples/include/overlapping-compat.h−8 servant-examples/servant-examples.cabal−148 servant-examples/socket-io-chat/Chat.hs−109 servant-examples/socket-io-chat/resources/index.html−28 servant-examples/socket-io-chat/resources/main.js−274 servant-examples/socket-io-chat/resources/style.css−150 servant-examples/socket-io-chat/socket-io-chat.hs−54 servant-examples/tinc.yaml−15 servant-examples/tutorial/T1.hs−45 servant-examples/tutorial/T10.hs−71 servant-examples/tutorial/T2.hs−52 servant-examples/tutorial/T3.hs−84 servant-examples/tutorial/T4.hs−63 servant-examples/tutorial/T5.hs−37 servant-examples/tutorial/T6.hs−18 servant-examples/tutorial/T7.hs−33 servant-examples/tutorial/T8.hs−49 servant-examples/tutorial/T9.hs−105 servant-examples/tutorial/t8-main.hs−4 servant-examples/tutorial/tutorial.hs−39 servant-examples/wai-middleware/wai-middleware.hs−51 sources.txt+1−1 stack.yaml+5−2
.gitignore+4
@@ -25,3 +25,7 @@ Setup
2525.stack-work
2626shell.nix
2727default.nix
28+doc/_build
29+doc/venv
30+doc/tutorial/static/api.js
31+doc/tutorial/static/jq.js
doc/Makefilenew file+216
@@ -0,0 +1,216 @@
1+# Makefile for Sphinx documentation
2+#
3+
4+# You can set these variables from the command line.
5+SPHINXOPTS =
6+SPHINXBUILD = sphinx-build
7+PAPER =
8+BUILDDIR = _build
9+
10+# User-friendly check for sphinx-build
11+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12+$(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/)
13+endif
14+
15+# Internal variables.
16+PAPEROPT_a4 = -D latex_paper_size=a4
17+PAPEROPT_letter = -D latex_paper_size=letter
18+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19+# the i18n builder cannot share the environment and doctrees with the others
20+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21+
22+.PHONY: help
23+help:
24+ @echo "Please use \`make <target>' where <target> is one of"
25+ @echo " html to make standalone HTML files"
26+ @echo " dirhtml to make HTML files named index.html in directories"
27+ @echo " singlehtml to make a single large HTML file"
28+ @echo " pickle to make pickle files"
29+ @echo " json to make JSON files"
30+ @echo " htmlhelp to make HTML files and a HTML help project"
31+ @echo " qthelp to make HTML files and a qthelp project"
32+ @echo " applehelp to make an Apple Help Book"
33+ @echo " devhelp to make HTML files and a Devhelp project"
34+ @echo " epub to make an epub"
35+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
37+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38+ @echo " text to make text files"
39+ @echo " man to make manual pages"
40+ @echo " texinfo to make Texinfo files"
41+ @echo " info to make Texinfo files and run them through makeinfo"
42+ @echo " gettext to make PO message catalogs"
43+ @echo " changes to make an overview of all changed/added/deprecated items"
44+ @echo " xml to make Docutils-native XML files"
45+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46+ @echo " linkcheck to check all external links for integrity"
47+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48+ @echo " coverage to run coverage check of the documentation (if enabled)"
49+
50+.PHONY: clean
51+clean:
52+ rm -rf $(BUILDDIR)/*
53+
54+.PHONY: html
55+html:
56+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
57+ @echo
58+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
59+
60+.PHONY: dirhtml
61+dirhtml:
62+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
63+ @echo
64+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
65+
66+.PHONY: singlehtml
67+singlehtml:
68+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
69+ @echo
70+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
71+
72+.PHONY: pickle
73+pickle:
74+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
75+ @echo
76+ @echo "Build finished; now you can process the pickle files."
77+
78+.PHONY: json
79+json:
80+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
81+ @echo
82+ @echo "Build finished; now you can process the JSON files."
83+
84+.PHONY: htmlhelp
85+htmlhelp:
86+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
87+ @echo
88+ @echo "Build finished; now you can run HTML Help Workshop with the" \
89+ ".hhp project file in $(BUILDDIR)/htmlhelp."
90+
91+.PHONY: qthelp
92+qthelp:
93+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
94+ @echo
95+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
96+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
97+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/generics-eot.qhcp"
98+ @echo "To view the help file:"
99+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/generics-eot.qhc"
100+
101+.PHONY: applehelp
102+applehelp:
103+ $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
104+ @echo
105+ @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
106+ @echo "N.B. You won't be able to view it unless you put it in" \
107+ "~/Library/Documentation/Help or install it in your application" \
108+ "bundle."
109+
110+.PHONY: devhelp
111+devhelp:
112+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
113+ @echo
114+ @echo "Build finished."
115+ @echo "To view the help file:"
116+ @echo "# mkdir -p $$HOME/.local/share/devhelp/generics-eot"
117+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/generics-eot"
118+ @echo "# devhelp"
119+
120+.PHONY: epub
121+epub:
122+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
123+ @echo
124+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
125+
126+.PHONY: latex
127+latex:
128+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
129+ @echo
130+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
131+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
132+ "(use \`make latexpdf' here to do that automatically)."
133+
134+.PHONY: latexpdf
135+latexpdf:
136+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
137+ @echo "Running LaTeX files through pdflatex..."
138+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
139+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
140+
141+.PHONY: latexpdfja
142+latexpdfja:
143+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
144+ @echo "Running LaTeX files through platex and dvipdfmx..."
145+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
146+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
147+
148+.PHONY: text
149+text:
150+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
151+ @echo
152+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
153+
154+.PHONY: man
155+man:
156+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
157+ @echo
158+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
159+
160+.PHONY: texinfo
161+texinfo:
162+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
163+ @echo
164+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
165+ @echo "Run \`make' in that directory to run these through makeinfo" \
166+ "(use \`make info' here to do that automatically)."
167+
168+.PHONY: info
169+info:
170+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
171+ @echo "Running Texinfo files through makeinfo..."
172+ make -C $(BUILDDIR)/texinfo info
173+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
174+
175+.PHONY: gettext
176+gettext:
177+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
178+ @echo
179+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
180+
181+.PHONY: changes
182+changes:
183+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
184+ @echo
185+ @echo "The overview file is in $(BUILDDIR)/changes."
186+
187+.PHONY: linkcheck
188+linkcheck:
189+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
190+ @echo
191+ @echo "Link check complete; look for any errors in the above output " \
192+ "or in $(BUILDDIR)/linkcheck/output.txt."
193+
194+.PHONY: doctest
195+doctest:
196+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
197+ @echo "Testing of doctests in the sources finished, look at the " \
198+ "results in $(BUILDDIR)/doctest/output.txt."
199+
200+.PHONY: coverage
201+coverage:
202+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
203+ @echo "Testing of coverage in the sources finished, look at the " \
204+ "results in $(BUILDDIR)/coverage/python.txt."
205+
206+.PHONY: xml
207+xml:
208+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
209+ @echo
210+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
211+
212+.PHONY: pseudoxml
213+pseudoxml:
214+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
215+ @echo
216+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
doc/building-the-docsnew file+8
@@ -0,0 +1,8 @@
1+To build the docs locally:
2+
3+$ virtualenv venv
4+$ . ./venv/bin/activate
5+$ pip install -r requirements.txt
6+$ make html
7+
8+Docs will be built in _build/html/index.html .
doc/conf.pynew file+294
@@ -0,0 +1,294 @@
1+# -*- coding: utf-8 -*-
2+#
3+# servant documentation build configuration file, created by
4+# sphinx-quickstart on Mon Nov 23 13:24:36 2015.
5+#
6+# This file is execfile()d with the current directory set to its
7+# containing dir.
8+#
9+# Note that not all possible configuration values are present in this
10+# autogenerated file.
11+#
12+# All configuration values have a default; values that are commented out
13+# serve to show the default.
14+
15+import sys
16+import os
17+import shlex
18+from recommonmark.parser import CommonMarkParser
19+
20+# If extensions (or modules to document with autodoc) are in another directory,
21+# add these directories to sys.path here. If the directory is relative to the
22+# documentation root, use os.path.abspath to make it absolute, like shown here.
23+#sys.path.insert(0, os.path.abspath('.'))
24+
25+# -- General configuration ------------------------------------------------
26+
27+# If your documentation needs a minimal Sphinx version, state it here.
28+#needs_sphinx = '1.0'
29+
30+# Add any Sphinx extension module names here, as strings. They can be
31+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
32+# ones.
33+extensions = []
34+
35+# Add any paths that contain templates here, relative to this directory.
36+templates_path = ['_templates']
37+
38+# The suffix(es) of source filenames.
39+# You can specify multiple suffix as a list of string:
40+source_suffix = ['.md', '.rst', '.lhs']
41+
42+# The encoding of source files.
43+#source_encoding = 'utf-8-sig'
44+
45+# The master toctree document.
46+master_doc = 'index'
47+
48+# General information about the project.
49+project = u'servant'
50+copyright = u'2016, Servant Contributors'
51+author = u'Servant Contributors'
52+
53+# The version info for the project you're documenting, acts as replacement for
54+# |version| and |release|, also used in various other places throughout the
55+# built documents.
56+#
57+# The short X.Y version.
58+# version = 'latest'
59+# The full version, including alpha/beta/rc tags.
60+# release = 'latest'
61+
62+# The language for content autogenerated by Sphinx. Refer to documentation
63+# for a list of supported languages.
64+#
65+# This is also used if you do content translation via gettext catalogs.
66+# Usually you set "language" from the command line for these cases.
67+language = None
68+
69+# There are two options for replacing |today|: either, you set today to some
70+# non-false value, then it is used:
71+#today = ''
72+# Else, today_fmt is used as the format for a strftime call.
73+#today_fmt = '%B %d, %Y'
74+
75+# List of patterns, relative to source directory, that match files and
76+# directories to ignore when looking for source files.
77+exclude_patterns = ['_build', 'venv']
78+
79+# The reST default role (used for this markup: `text`) to use for all
80+# documents.
81+#default_role = None
82+
83+# If true, '()' will be appended to :func: etc. cross-reference text.
84+#add_function_parentheses = True
85+
86+# If true, the current module name will be prepended to all description
87+# unit titles (such as .. function::).
88+#add_module_names = True
89+
90+# If true, sectionauthor and moduleauthor directives will be shown in the
91+# output. They are ignored by default.
92+#show_authors = False
93+
94+# The name of the Pygments (syntax highlighting) style to use.
95+pygments_style = 'sphinx'
96+
97+def setup(app):
98+ from sphinx.highlighting import lexers
99+ from pygments.lexers import HaskellLexer
100+ lexers['haskell ignore'] = HaskellLexer(stripnl=False)
101+
102+# A list of ignored prefixes for module index sorting.
103+#modindex_common_prefix = []
104+
105+# If true, keep warnings as "system message" paragraphs in the built documents.
106+#keep_warnings = False
107+
108+# If true, `todo` and `todoList` produce output, else they produce nothing.
109+todo_include_todos = False
110+
111+
112+# -- Options for HTML output ----------------------------------------------
113+
114+# The theme to use for HTML and HTML Help pages. See the documentation for
115+# a list of builtin themes.
116+html_theme = 'sphinx_rtd_theme'
117+
118+# Theme options are theme-specific and customize the look and feel of a theme
119+# further. For a list of options available for each theme, see the
120+# documentation.
121+#html_theme_options = {}
122+
123+# Add any paths that contain custom themes here, relative to this directory.
124+#html_theme_path = []
125+
126+# The name for this set of Sphinx documents. If None, it defaults to
127+# "<project> v<release> documentation".
128+#html_title = None
129+
130+# A shorter title for the navigation bar. Default is the same as html_title.
131+#html_short_title = None
132+
133+# The name of an image file (relative to this directory) to place at the top
134+# of the sidebar.
135+#html_logo = None
136+
137+# The name of an image file (within the static path) to use as favicon of the
138+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
139+# pixels large.
140+#html_favicon = None
141+
142+# Add any paths that contain custom static files (such as style sheets) here,
143+# relative to this directory. They are copied after the builtin static files,
144+# so a file named "default.css" will overwrite the builtin "default.css".
145+html_static_path = ['_static']
146+
147+# Add any extra paths that contain custom files (such as robots.txt or
148+# .htaccess) here, relative to this directory. These files are copied
149+# directly to the root of the documentation.
150+#html_extra_path = []
151+
152+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
153+# using the given strftime format.
154+#html_last_updated_fmt = '%b %d, %Y'
155+
156+# If true, SmartyPants will be used to convert quotes and dashes to
157+# typographically correct entities.
158+#html_use_smartypants = True
159+
160+# Custom sidebar templates, maps document names to template names.
161+#html_sidebars = {}
162+
163+# Additional templates that should be rendered to pages, maps page names to
164+# template names.
165+#html_additional_pages = {}
166+
167+# If false, no module index is generated.
168+#html_domain_indices = True
169+
170+# If false, no index is generated.
171+#html_use_index = True
172+
173+# If true, the index is split into individual pages for each letter.
174+#html_split_index = False
175+
176+# If true, links to the reST sources are added to the pages.
177+#html_show_sourcelink = True
178+
179+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
180+#html_show_sphinx = True
181+
182+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
183+#html_show_copyright = True
184+
185+# If true, an OpenSearch description file will be output, and all pages will
186+# contain a <link> tag referring to it. The value of this option must be the
187+# base URL from which the finished HTML is served.
188+#html_use_opensearch = ''
189+
190+# This is the file name suffix for HTML files (e.g. ".xhtml").
191+#html_file_suffix = None
192+
193+# Language to be used for generating the HTML full-text search index.
194+# Sphinx supports the following languages:
195+# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
196+# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
197+#html_search_language = 'en'
198+
199+# A dictionary with options for the search language support, empty by default.
200+# Now only 'ja' uses this config value
201+#html_search_options = {'type': 'default'}
202+
203+# The name of a javascript file (relative to the configuration directory) that
204+# implements a search results scorer. If empty, the default will be used.
205+#html_search_scorer = 'scorer.js'
206+
207+# Output file base name for HTML help builder.
208+htmlhelp_basename = 'servantdoc'
209+
210+# -- Options for LaTeX output ---------------------------------------------
211+
212+latex_elements = {
213+# The paper size ('letterpaper' or 'a4paper').
214+#'papersize': 'letterpaper',
215+
216+# The font size ('10pt', '11pt' or '12pt').
217+#'pointsize': '10pt',
218+
219+# Additional stuff for the LaTeX preamble.
220+#'preamble': '',
221+
222+# Latex figure (float) alignment
223+#'figure_align': 'htbp',
224+}
225+
226+# Grouping the document tree into LaTeX files. List of tuples
227+# (source start file, target name, title,
228+# author, documentclass [howto, manual, or own class]).
229+latex_documents = [
230+ (master_doc, 'servant.tex', u'servant Documentation',
231+ u'Servant Contributors', 'manual'),
232+]
233+
234+# The name of an image file (relative to this directory) to place at the top of
235+# the title page.
236+#latex_logo = None
237+
238+# For "manual" documents, if this is true, then toplevel headings are parts,
239+# not chapters.
240+#latex_use_parts = False
241+
242+# If true, show page references after internal links.
243+#latex_show_pagerefs = False
244+
245+# If true, show URL addresses after external links.
246+#latex_show_urls = False
247+
248+# Documents to append as an appendix to all manuals.
249+#latex_appendices = []
250+
251+# If false, no module index is generated.
252+#latex_domain_indices = True
253+
254+
255+# -- Options for manual page output ---------------------------------------
256+
257+# One entry per manual page. List of tuples
258+# (source start file, name, description, authors, manual section).
259+man_pages = [
260+ (master_doc, 'servant', u'servant Documentation',
261+ [author], 1)
262+]
263+
264+# If true, show URL addresses after external links.
265+#man_show_urls = False
266+
267+
268+# -- Options for Texinfo output -------------------------------------------
269+
270+# Grouping the document tree into Texinfo files. List of tuples
271+# (source start file, target name, title, author,
272+# dir menu entry, description, category)
273+texinfo_documents = [
274+ (master_doc, 'servant', u'servant Documentation',
275+ author, 'servant', 'One line description of project.',
276+ 'Miscellaneous'),
277+]
278+
279+# Documents to append as an appendix to all manuals.
280+#texinfo_appendices = []
281+
282+# If false, no module index is generated.
283+#texinfo_domain_indices = True
284+
285+# How to display URL addresses: 'footnote', 'no', or 'inline'.
286+#texinfo_show_urls = 'footnote'
287+
288+# If true, do not generate a @detailmenu in the "Top" node's menu.
289+#texinfo_no_detailmenu = False
290+
291+source_parsers = {
292+ '.md': CommonMarkParser,
293+ '.lhs': CommonMarkParser,
294+}
doc/index.rstnew file+22
@@ -0,0 +1,22 @@
1+servant – A Type-Level Web DSL
2+==============================
3+
4+.. image:: https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png
5+
6+**servant** is a set of packages for declaring web APIs at the type-level and
7+then using those API specifications to:
8+
9+- write servers (this part of **servant** can be considered a web framework),
10+- obtain client functions (in haskell),
11+- generate client functions for other programming languages,
12+- generate documentation for your web applications
13+- and more...
14+
15+All in a type-safe manner.
16+
17+.. toctree::
18+ :maxdepth: 2
19+
20+ introduction.rst
21+ tutorial/index.rst
22+ links.rst
doc/introduction.rstnew file+40
@@ -0,0 +1,40 @@
1+Introduction
2+------------
3+
4+**servant** has the following guiding principles:
5+
6+- concision
7+
8+ This is a pretty wide-ranging principle. You should be able to get nice
9+ documentation for your web servers, and client libraries, without repeating
10+ yourself. You should not have to manually serialize and deserialize your
11+ resources, but only declare how to do those things *once per type*. If a
12+ bunch of your handlers take the same query parameters, you shouldn't have to
13+ repeat that logic for each handler, but instead just "apply" it to all of
14+ them at once. Your handlers shouldn't be where composition goes to die. And
15+ so on.
16+
17+- flexibility
18+
19+ If we haven't thought of your use case, it should still be easily
20+ achievable. If you want to use templating library X, go ahead. Forms? Do
21+ them however you want, but without difficulty. We're not opinionated.
22+
23+- separation of concerns
24+
25+ Your handlers and your HTTP logic should be separate. True to the philosphy
26+ at the core of HTTP and REST, with **servant** your handlers return normal
27+ Haskell datatypes - that's the resource. And then from a description of your
28+ API, **servant** handles the *presentation* (i.e., the Content-Types). But
29+ that's just one example.
30+
31+- type safety
32+
33+ Want to be sure your API meets a specification? Your compiler can check
34+ that for you. Links you can be sure exist? You got it.
35+
36+To stick true to these principles, we do things a little differently than you
37+might expect. The core idea is *reifying the description of your API*. Once
38+reified, everything follows. We think we might be the first web framework to
39+reify API descriptions in an extensible way. We're pretty sure we're the first
40+to reify it as *types*.
doc/links.rstnew file+34
@@ -0,0 +1,34 @@
1+
2+Helpful Links
3+-------------
4+
5+- the central documentation (this site):
6+ `haskell-servant.readthedocs.org <http://haskell-servant.readthedocs.org/>`_
7+
8+- the github repo:
9+ `github.com/haskell-servant/servant <https://github.com/haskell-servant/servant>`_
10+
11+- the issue tracker (Feel free to create issues and submit PRs!):
12+ `https://github.com/haskell-servant/servant/issues <https://github.com/haskell-servant/servant/issues>`_
13+
14+- the irc channel:
15+ ``#servant`` on freenode
16+
17+- the mailing list:
18+ `groups.google.com/forum/#!forum/haskell-servant <https://groups.google.com/forum/#!forum/haskell-servant>`_
19+
20+- blog posts and videos and slides of some talks on servant:
21+ `haskell-servant.github.io <http://haskell-servant.github.io>`_
22+
23+- the servant packages on hackage:
24+
25+ - `hackage.haskell.org/package/servant <http://hackage.haskell.org/package/servant>`_
26+ - `hackage.haskell.org/package/servant-server <http://hackage.haskell.org/package/servant-server>`_
27+ - `hackage.haskell.org/package/servant-client <http://hackage.haskell.org/package/servant-client>`_
28+ - `hackage.haskell.org/package/servant-blaze <http://hackage.haskell.org/package/servant-blaze>`_
29+ - `hackage.haskell.org/package/servant-lucid <http://hackage.haskell.org/package/servant-lucid>`_
30+ - `hackage.haskell.org/package/servant-cassava <http://hackage.haskell.org/package/servant-cassava>`_
31+ - `hackage.haskell.org/package/servant-docs <http://hackage.haskell.org/package/servant-docs>`_
32+ - `hackage.haskell.org/package/servant-foreign <http://hackage.haskell.org/package/servant-foreign>`_
33+ - `hackage.haskell.org/package/servant-js <http://hackage.haskell.org/package/servant-js>`_
34+ - `hackage.haskell.org/package/servant-mock <http://hackage.haskell.org/package/servant-mock>`_
doc/requirements.txtnew file+25
@@ -0,0 +1,25 @@
1+alabaster==0.7.7
2+argh==0.26.1
3+Babel==2.2.0
4+backports-abc==0.4
5+backports.ssl-match-hostname==3.5.0.1
6+certifi==2015.11.20.1
7+CommonMark==0.5.4
8+docutils==0.12
9+Jinja2==2.8
10+livereload==2.4.1
11+MarkupSafe==0.23
12+pathtools==0.1.2
13+Pygments==2.1.1
14+pytz==2015.7
15+PyYAML==3.11
16+recommonmark==0.4.0
17+singledispatch==3.4.0.3
18+six==1.10.0
19+snowballstemmer==1.2.1
20+Sphinx==1.3.4
21+sphinx-autobuild==0.5.2
22+sphinx-rtd-theme==0.1.9
23+tornado==4.3
24+watchdog==0.8.3
25+wheel==0.26.0
doc/tutorial/.ghcinew file+1
@@ -0,0 +1 @@
1+:set -pgmL markdown-unlit -Wall -Werror -fno-warn-missing-methods -fno-warn-name-shadowing -itest
doc/tutorial/ApiType.lhsnew file+311
@@ -0,0 +1,311 @@
1+# A web API as a type
2+
3+The source for this tutorial section is a literate haskell file, so first we
4+need to have some language extensions and imports:
5+
6+``` haskell
7+{-# LANGUAGE DataKinds #-}
8+{-# LANGUAGE TypeOperators #-}
9+
10+module ApiType where
11+
12+import Data.Text
13+import Servant.API
14+```
15+
16+Consider the following informal specification of an API:
17+
18+ > The endpoint at `/users` expects a GET request with query string parameter
19+ > `sortby` whose value can be one of `age` or `name` and returns a
20+ > list/array of JSON objects describing users, with fields `age`, `name`,
21+ > `email`, `registration_date`".
22+
23+You *should* be able to formalize that. And then use the formalized version to
24+get you much of the way towards writing a web app. And all the way towards
25+getting some client libraries, and documentation, and more.
26+
27+How would we describe it with **servant**? An endpoint description is a good old
28+Haskell **type**:
29+
30+``` haskell
31+type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
32+
33+data SortBy = Age | Name
34+
35+data User = User {
36+ name :: String,
37+ age :: Int
38+}
39+```
40+
41+Let's break that down:
42+
43+- `"users"` says that our endpoint will be accessible under `/users`;
44+- `QueryParam "sortby" SortBy`, where `SortBy` is defined by `data SortBy = Age | Name`,
45+ says that the endpoint has a query string parameter named `sortby`
46+ whose value will be extracted as a value of type `SortBy`.
47+- `Get '[JSON] [User]` says that the endpoint will be accessible through HTTP
48+ GET requests, returning a list of users encoded as JSON. You will see
49+ later how you can make use of this to make your data available under different
50+ formats, the choice being made depending on the [Accept
51+ header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) specified in
52+ the client's request.
53+- The `:>` operator that separates the various "combinators" just lets you
54+ sequence static path fragments, URL captures and other combinators. The
55+ ordering only matters for static path fragments and URL captures. `"users" :>
56+ "list-all" :> Get '[JSON] [User]`, equivalent to `/users/list-all`, is
57+ obviously not the same as `"list-all" :> "users" :> Get '[JSON] [User]`, which
58+ is equivalent to `/list-all/users`. This means that sometimes `:>` is somehow
59+ equivalent to `/`, but sometimes it just lets you chain another combinator.
60+
61+We can also describe APIs with multiple endpoints by using the `:<|>`
62+combinators. Here's an example:
63+
64+``` haskell
65+type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
66+ :<|> "list-all" :> "users" :> Get '[JSON] [User]
67+```
68+
69+**servant** provides a fair amount of combinators out-of-the-box, but you can
70+always write your own when you need it. Here's a quick overview of the most
71+often needed the combinators that **servant** comes with.
72+
73+## Combinators
74+
75+### Static strings
76+
77+As you've already seen, you can use type-level strings (enabled with the
78+`DataKinds` language extension) for static path fragments. Chaining
79+them amounts to `/`-separating them in a URL.
80+
81+``` haskell
82+type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
83+ -- describes an endpoint reachable at:
84+ -- /users/list-all/now
85+```
86+
87+### `Delete`, `Get`, `Patch`, `Post` and `Put`
88+
89+The `Get` combinator is defined in terms of the more general `Verb`:
90+``` haskell ignore
91+data Verb method (statusCode :: Nat) (contentType :: [*]) a
92+type Get = Verb 'GET 200
93+```
94+
95+There are other predefined type synonyms for other common HTTP methods,
96+such as e.g.:
97+``` haskell ignore
98+data Delete = Verb 'DELETE 200
99+data Patch = Verb 'PATCH 200
100+data Post = Verb 'POST 200
101+data Put = Verb 'PUT 200
102+```
103+
104+There are also variants that do not return a 200 status code, such
105+as for example:
106+``` haskell ignore
107+type PostCreated = Verb 'POST 201
108+type PostAccepted = Verb 'POST 202
109+```
110+
111+An endpoint always ends with a variant of the `Verb` combinator
112+(unless you write your own combinators). Examples:
113+
114+``` haskell
115+type UserAPI4 = "users" :> Get '[JSON] [User]
116+ :<|> "admins" :> Get '[JSON] [User]
117+```
118+
119+### `Capture`
120+
121+URL captures are segments of the path of a URL that are variable and whose actual value is
122+captured and passed to the request handlers. In many web frameworks, you'll see
123+it written as in `/users/:userid`, with that leading `:` denoting that `userid`
124+is just some kind of variable name or placeholder. For instance, if `userid` is
125+supposed to range over all integers greater or equal to 1, our endpoint will
126+match requests made to `/users/1`, `/users/143` and so on.
127+
128+The `Capture` combinator in **servant** takes a (type-level) string representing
129+the "name of the variable" and a type, which indicates the type we want to
130+decode the "captured value" to.
131+
132+``` haskell ignore
133+data Capture (s :: Symbol) a
134+-- s :: Symbol just says that 's' must be a type-level string.
135+```
136+
137+In some web frameworks, you use regexes for captures. We use a
138+[`FromText`](https://hackage.haskell.org/package/servant/docs/Servant-Common-Text.html#t:FromText)
139+class, which the captured value must be an instance of.
140+
141+Examples:
142+
143+``` haskell
144+type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
145+ -- equivalent to 'GET /user/:userid'
146+ -- except that we explicitly say that "userid"
147+ -- must be an integer
148+
149+ :<|> "user" :> Capture "userid" Integer :> DeleteNoContent '[JSON] NoContent
150+ -- equivalent to 'DELETE /user/:userid'
151+```
152+
153+In the second case, `DeleteNoContent` specifies a 204 response code,
154+`JSON` specifies the content types on which the handler will match,
155+and `NoContent` says that the response will always be empty.
156+
157+### `QueryParam`, `QueryParams`, `QueryFlag`
158+
159+`QueryParam`, `QueryParams` and `QueryFlag` are about parameters in the query string,
160+i.e., those parameters that come after the question mark
161+(`?`) in URLs, like `sortby` in `/users?sortby=age`, whose value is
162+set to `age`. `QueryParams` lets you specify that the query parameter
163+is actually a list of values, which can be specified using
164+`?param=value1&param=value2`. This represents a list of values
165+composed of `value1` and `value2`. `QueryFlag` lets you specify a
166+boolean-like query parameter where a client isn't forced to specify a
167+value. The absence or presence of the parameter's name in the query
168+string determines whether the parameter is considered to have the
169+value `True` or `False`. For instance, `/users?active` would list only
170+active users whereas `/users` would list them all.
171+
172+Here are the corresponding data type declarations:
173+
174+``` haskell ignore
175+data QueryParam (sym :: Symbol) a
176+data QueryParams (sym :: Symbol) a
177+data QueryFlag (sym :: Symbol)
178+```
179+
180+Examples:
181+
182+``` haskell
183+type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
184+ -- equivalent to 'GET /users?sortby={age, name}'
185+
186+```
187+
188+Again, your handlers don't have to deserialize these things (into, for example,
189+a `SortBy`). **servant** takes care of it.
190+
191+### `ReqBody`
192+
193+Each HTTP request can carry some additional data that the server can use in its
194+*body*, and this data can be encoded in any format -- as long as the server
195+understands it. This can be used for example for an endpoint for creating new
196+users: instead of passing each field of the user as a separate query string
197+parameter or something dirty like that, we can group all the data into a JSON
198+object. This has the advantage of supporting nested objects.
199+
200+**servant**'s `ReqBody` combinator takes a list of content types in which the
201+data encoded in the request body can be represented and the type of that data.
202+And, as you might have guessed, you don't have to check the content type
203+header, and do the deserialization yourself. We do it for you. And return `Bad
204+Request` or `Unsupported Content Type` as appropriate.
205+
206+Here's the data type declaration for it:
207+
208+``` haskell ignore
209+data ReqBody (contentTypes :: [*]) a
210+```
211+
212+Examples:
213+
214+``` haskell
215+type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
216+ -- - equivalent to 'POST /users' with a JSON object
217+ -- describing a User in the request body
218+ -- - returns a User encoded in JSON
219+
220+ :<|> "users" :> Capture "userid" Integer
221+ :> ReqBody '[JSON] User
222+ :> Put '[JSON] User
223+ -- - equivalent to 'PUT /users/:userid' with a JSON
224+ -- object describing a User in the request body
225+ -- - returns a User encoded in JSON
226+```
227+
228+### Request `Header`s
229+
230+Request headers are used for various purposes, from caching to carrying
231+auth-related data. They consist of a header name and an associated value. An
232+example would be `Accept: application/json`.
233+
234+The `Header` combinator in **servant** takes a type-level string for the header
235+name and the type to which we want to decode the header's value (from some
236+textual representation), as illustrated below:
237+
238+``` haskell ignore
239+data Header (sym :: Symbol) a
240+```
241+
242+Here's an example where we declare that an endpoint makes use of the
243+`User-Agent` header which specifies the name of the software/library used by
244+the client to send the request.
245+
246+``` haskell
247+type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
248+```
249+
250+### Content types
251+
252+So far, whenever we have used a combinator that carries a list of content
253+types, we've always specified `'[JSON]`. However, **servant** lets you use several
254+content types, and also lets you define your own content types.
255+
256+Four content types are provided out-of-the-box by the core **servant** package:
257+`JSON`, `PlainText`, `FormUrlEncoded` and `OctetStream`. If for some obscure
258+reason you wanted one of your endpoints to make your user data available under
259+those 4 formats, you would write the API type as below:
260+
261+``` haskell
262+type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
263+```
264+
265+(There are other packages that provide other content types. For example
266+**servant-lucid** and **servant-blaze** allow to generate html pages (using
267+**lucid** and **blaze-html**) and both come with a content type for html.)
268+
269+We will further explain how these content types and your data types can play
270+together in the [section about serving an API](Server.html).
271+
272+### Response `Headers`
273+
274+Just like an HTTP request, the response generated by a webserver can carry
275+headers too. **servant** provides a `Headers` combinator that carries a list of
276+`Header` types and can be used by simply wrapping the "return type" of an endpoint
277+with it.
278+
279+``` haskell ignore
280+data Headers (ls :: [*]) a
281+```
282+
283+If you want to describe an endpoint that returns a "User-Count" header in each
284+response, you could write it as below:
285+
286+``` haskell
287+type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
288+```
289+
290+### Interoperability with `wai`: `Raw`
291+
292+Finally, we also include a combinator named `Raw` that provides an escape hatch
293+to the underlying low-level web library `wai`. It can be used when
294+you want to plug a [wai `Application`](http://hackage.haskell.org/package/wai)
295+into your webservice:
296+
297+``` haskell
298+type UserAPI11 = "users" :> Get '[JSON] [User]
299+ -- a /users endpoint
300+
301+ :<|> Raw
302+ -- requests to anything else than /users
303+ -- go here, where the server will try to
304+ -- find a file with the right name
305+ -- at the right path
306+```
307+
308+One example for this is if you want to serve a directory of static files along
309+with the rest of your API. But you can plug in everything that is an
310+`Application`, e.g. a whole web application written in any of the web
311+frameworks that support `wai`.
doc/tutorial/Client.lhsnew file+156
@@ -0,0 +1,156 @@
1+# Querying an API
2+
3+While defining handlers that serve an API has a lot to it, querying an API is simpler: we do not care about what happens inside the webserver, we just need to know how to talk to it and get a response back. Except that we usually have to write the querying functions by hand because the structure of the API isn't a first class citizen and can't be inspected to generate a bunch of client-side functions.
4+
5+**servant** however has a way to inspect APIs, because APIs are just Haskell types and (GHC) Haskell lets us do quite a few things with types. In the same way that we look at an API type to deduce the types the handlers should have, we can inspect the structure of the API to *derive* Haskell functions that take one argument for each occurence of `Capture`, `ReqBody`, `QueryParam`
6+and friends. By *derive*, we mean that there's no code generation involved, the functions are defined just by the structure of the API type.
7+
8+The source for this tutorial section is a literate haskell file, so first we
9+need to have some language extensions and imports:
10+
11+``` haskell
12+{-# LANGUAGE DataKinds #-}
13+{-# LANGUAGE DeriveGeneric #-}
14+{-# LANGUAGE TypeOperators #-}
15+
16+module Client where
17+
18+import Control.Monad.Trans.Except
19+import Data.Aeson
20+import Data.Proxy
21+import GHC.Generics
22+import Network.HTTP.Client (Manager, newManager, defaultManagerSettings)
23+import Servant.API
24+import Servant.Client
25+import System.IO.Unsafe
26+```
27+
28+Also, we need examples for some domain specific data types:
29+
30+``` haskell
31+data Position = Position
32+ { x :: Int
33+ , y :: Int
34+ } deriving (Show, Generic)
35+
36+instance FromJSON Position
37+
38+newtype HelloMessage = HelloMessage { msg :: String }
39+ deriving (Show, Generic)
40+
41+instance FromJSON HelloMessage
42+
43+data ClientInfo = ClientInfo
44+ { clientName :: String
45+ , clientEmail :: String
46+ , clientAge :: Int
47+ , clientInterestedIn :: [String]
48+ } deriving Generic
49+
50+instance ToJSON ClientInfo
51+
52+data Email = Email
53+ { from :: String
54+ , to :: String
55+ , subject :: String
56+ , body :: String
57+ } deriving (Show, Generic)
58+
59+instance FromJSON Email
60+```
61+
62+Enough chitchat, let's see an example. Consider the following API type from the previous section:
63+
64+``` haskell
65+type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
66+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
67+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
68+```
69+
70+What we are going to get with **servant-client** here is 3 functions, one to query each endpoint:
71+
72+``` haskell
73+position :: Int -- ^ value for "x"
74+ -> Int -- ^ value for "y"
75+ -> ExceptT ServantError IO Position
76+
77+hello :: Maybe String -- ^ an optional value for "name"
78+ -> ExceptT ServantError IO HelloMessage
79+
80+marketing :: ClientInfo -- ^ value for the request body
81+ -> ExceptT ServantError IO Email
82+```
83+
84+Each function makes available as an argument any value that the response may
85+depend on, as evidenced in the API type. How do we get these functions? By calling
86+the function `client`. It takes three arguments:
87+
88+- a `Proxy` to your API,
89+- a `BaseUrl`, consisting of the protocol, the host, the port and an optional subpath --
90+ this basically tells `client` where the service that you want to query is hosted,
91+- a `Manager`, (from [http-client](http://hackage.haskell.org/package/http-client))
92+which manages http connections.
93+
94+``` haskell
95+api :: Proxy API
96+api = Proxy
97+
98+{-# NOINLINE __manager #-}
99+__manager :: Manager
100+__manager = unsafePerformIO $ newManager defaultManagerSettings
101+
102+position :<|> hello :<|> marketing =
103+ client api (BaseUrl Http "localhost" 8081 "") __manager
104+```
105+
106+(Yes, the usage of `unsafePerformIO` is very ugly, we know. Hopefully soon it'll
107+be possible to do without.)
108+
109+As you can see in the code above, we just "pattern match our way" to these functions. If we try to derive less or more functions than there are endpoints in the API, we obviously get an error. The `BaseUrl` value there is just:
110+
111+``` haskell ignore
112+-- | URI scheme to use
113+data Scheme =
114+ Http -- ^ http://
115+ | Https -- ^ https://
116+ deriving
117+
118+-- | Simple data type to represent the target of HTTP requests
119+-- for servant's automatically-generated clients.
120+data BaseUrl = BaseUrl
121+ { baseUrlScheme :: Scheme -- ^ URI scheme to use
122+ , baseUrlHost :: String -- ^ host (eg "haskell.org")
123+ , baseUrlPort :: Int -- ^ port (eg 80)
124+ }
125+```
126+
127+That's it. Let's now write some code that uses our client functions.
128+
129+``` haskell
130+queries :: ExceptT ServantError IO (Position, HelloMessage, Email)
131+queries = do
132+ pos <- position 10 10
133+ message <- hello (Just "servant")
134+ em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
135+ return (pos, message, em)
136+
137+run :: IO ()
138+run = do
139+ res <- runExceptT queries
140+ case res of
141+ Left err -> putStrLn $ "Error: " ++ show err
142+ Right (pos, message, em) -> do
143+ print pos
144+ print message
145+ print em
146+```
147+
148+Here's the output of the above code running against the appropriate server:
149+
150+``` bash
151+Position {x = 10, y = 10}
152+HelloMessage {msg = "Hello, servant"}
153+Email {from = "great@company.com", to = "alp@foo.com", subject = "Hey Alp, we miss you!", body = "Hi Alp,\n\nSince you've recently turned 26, have you checked out our latest haskell, mathematics products? Give us a visit!"}
154+```
155+
156+The types of the arguments for the functions are the same as for (server-side) request handlers. You now know how to use **servant-client**!
doc/tutorial/Docs.lhsnew file+234
@@ -0,0 +1,234 @@
1+# Documenting an API
2+
3+The source for this tutorial section is a literate haskell file, so first we
4+need to have some language extensions and imports:
5+
6+``` haskell
7+{-# LANGUAGE DataKinds #-}
8+{-# LANGUAGE DeriveGeneric #-}
9+{-# LANGUAGE FlexibleInstances #-}
10+{-# LANGUAGE MultiParamTypeClasses #-}
11+{-# LANGUAGE OverloadedStrings #-}
12+{-# LANGUAGE TypeOperators #-}
13+{-# OPTIONS_GHC -fno-warn-orphans #-}
14+
15+module Docs where
16+
17+import Data.ByteString.Lazy (ByteString)
18+import Data.Proxy
19+import Data.Text.Lazy.Encoding (encodeUtf8)
20+import Data.Text.Lazy (pack)
21+import Network.HTTP.Types
22+import Network.Wai
23+import Servant.API
24+import Servant.Docs
25+import Servant.Server
26+```
27+
28+And we'll import some things from one of our earlier modules
29+([Serving an API](Server.html)):
30+
31+``` haskell
32+import Server (Email(..), ClientInfo(..), Position(..), HelloMessage(..),
33+ server3, emailForClient)
34+```
35+
36+Like client function generation, documentation generation amounts to inspecting the API type and extracting all the data we need to then present it in some format to users of your API.
37+
38+This time however, we have to assist **servant**. While it is able to deduce a lot of things about our API, it can't magically come up with descriptions of the various pieces of our APIs that are human-friendly and explain what's going on "at the business-logic level". A good example to study for documentation generation is our webservice with the `/position`, `/hello` and `/marketing` endpoints from earlier:
39+
40+``` haskell
41+type ExampleAPI = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
42+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
43+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
44+
45+exampleAPI :: Proxy ExampleAPI
46+exampleAPI = Proxy
47+```
48+
49+While **servant** can see e.g. that there are 3 endpoints and that the response bodies will be in JSON, it doesn't know what influence the captures, parameters, request bodies and other combinators have on the webservice. This is where some manual work is required.
50+
51+For every capture, request body, response body, query param, we have to give some explanations about how it influences the response, what values are possible and the likes. Here's how it looks like for the parameters we have above.
52+
53+``` haskell
54+instance ToCapture (Capture "x" Int) where
55+ toCapture _ =
56+ DocCapture "x" -- name
57+ "(integer) position on the x axis" -- description
58+
59+instance ToCapture (Capture "y" Int) where
60+ toCapture _ =
61+ DocCapture "y" -- name
62+ "(integer) position on the y axis" -- description
63+
64+instance ToSample Position where
65+ toSamples _ = singleSample (Position 3 14) -- example of output
66+
67+instance ToParam (QueryParam "name" String) where
68+ toParam _ =
69+ DocQueryParam "name" -- name
70+ ["Alp", "John Doe", "..."] -- example of values (not necessarily exhaustive)
71+ "Name of the person to say hello to." -- description
72+ Normal -- Normal, List or Flag
73+
74+instance ToSample HelloMessage where
75+ toSamples _ =
76+ [ ("When a value is provided for 'name'", HelloMessage "Hello, Alp")
77+ , ("When 'name' is not specified", HelloMessage "Hello, anonymous coward")
78+ ]
79+ -- mutliple examples to display this time
80+
81+ci :: ClientInfo
82+ci = ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
83+
84+instance ToSample ClientInfo where
85+ toSamples _ = singleSample ci
86+
87+instance ToSample Email where
88+ toSamples _ = singleSample (emailForClient ci)
89+```
90+
91+Types that are used as request or response bodies have to instantiate the `ToSample` typeclass which lets you specify one or more examples of values. `Capture`s and `QueryParam`s have to instantiate their respective `ToCapture` and `ToParam` classes and provide a name and some information about the concrete meaning of that argument, as illustrated in the code above.
92+
93+With all of this, we can derive docs for our API.
94+
95+``` haskell
96+apiDocs :: API
97+apiDocs = docs exampleAPI
98+```
99+
100+`API` is a type provided by **servant-docs** that stores all the information one needs about a web API in order to generate documentation in some format. Out of the box, **servant-docs** only provides a pretty documentation printer that outputs [Markdown](http://en.wikipedia.org/wiki/Markdown), but the [**servant-pandoc**](http://hackage.haskell.org/package/servant-pandoc) package can be used to target many useful formats.
101+
102+**servant**'s markdown pretty printer is a function named `markdown`.
103+
104+``` haskell ignore
105+markdown :: API -> String
106+```
107+
108+That lets us see what our API docs look down in markdown, by looking at `markdown apiDocs`.
109+
110+````````` text
111+## Welcome
112+
113+This is our super webservice's API.
114+
115+Enjoy!
116+
117+## GET /hello
118+
119+#### GET Parameters:
120+
121+- name
122+ - **Values**: *Alp, John Doe, ...*
123+ - **Description**: Name of the person to say hello to.
124+
125+
126+#### Response:
127+
128+- Status code 200
129+- Headers: []
130+
131+- Supported content types are:
132+
133+ - `application/json`
134+
135+- When a value is provided for 'name'
136+
137+ ```javascript
138+ {"msg":"Hello, Alp"}
139+ ```
140+
141+- When 'name' is not specified
142+
143+ ```javascript
144+ {"msg":"Hello, anonymous coward"}
145+ ```
146+
147+## POST /marketing
148+
149+#### Request:
150+
151+- Supported content types are:
152+
153+ - `application/json`
154+
155+- Example: `application/json`
156+
157+ ```javascript
158+ {"email":"alp@foo.com","interested_in":["haskell","mathematics"],"age":26,"name":"Alp"}
159+ ```
160+
161+#### Response:
162+
163+- Status code 201
164+- Headers: []
165+
166+- Supported content types are:
167+
168+ - `application/json`
169+
170+- Response body as below.
171+
172+ ```javascript
173+ {"subject":"Hey Alp, we miss you!","body":"Hi Alp,\n\nSince you've recently turned 26, have you checked out our latest haskell, mathematics products? Give us a visit!","to":"alp@foo.com","from":"great@company.com"}
174+ ```
175+
176+## GET /position/:x/:y
177+
178+#### Captures:
179+
180+- *x*: (integer) position on the x axis
181+- *y*: (integer) position on the y axis
182+
183+#### Response:
184+
185+- Status code 200
186+- Headers: []
187+
188+- Supported content types are:
189+
190+ - `application/json`
191+
192+- Response body as below.
193+
194+ ```javascript
195+ {"x":3,"y":14}
196+ ```
197+
198+`````````
199+
200+However, we can also add one or more introduction sections to the document. We just need to tweak the way we generate `apiDocs`. We will also convert the content to a lazy `ByteString` since this is what **wai** expects for `Raw` endpoints.
201+
202+``` haskell
203+docsBS :: ByteString
204+docsBS = encodeUtf8
205+ . pack
206+ . markdown
207+ $ docsWithIntros [intro] exampleAPI
208+
209+ where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]
210+```
211+
212+`docsWithIntros` just takes an additional parameter, a list of `DocIntro`s that must be displayed before any endpoint docs.
213+
214+We can now serve the API *and* the API docs with a simple server.
215+
216+``` haskell
217+type DocsAPI = ExampleAPI :<|> Raw
218+
219+api :: Proxy DocsAPI
220+api = Proxy
221+
222+server :: Server DocsAPI
223+server = Server.server3 :<|> serveDocs
224+
225+ where serveDocs _ respond =
226+ respond $ responseLBS ok200 [plain] docsBS
227+
228+ plain = ("Content-Type", "text/plain")
229+
230+app :: Application
231+app = serve api server
232+```
233+
234+And if you spin up this server and request anything else than `/position`, `/hello` and `/marketing`, you will see the API docs in markdown. This is because `serveDocs` is attempted if the 3 other endpoints don't match and systematically succeeds since its definition is to just return some fixed bytestring with the `text/plain` content type.
doc/tutorial/Javascript.lhsnew file+228
@@ -0,0 +1,228 @@
1+# Generating Javascript functions to query an API
2+
3+We will now see how **servant** lets you turn an API type into javascript
4+functions that you can call to query a webservice.
5+
6+For this, we will consider a simple page divided in two parts. At the top, we
7+will have a search box that lets us search in a list of Haskell books by
8+author/title with a list of results that gets updated every time we enter or
9+remove a character, while at the bottom we will be able to see the classical
10+[probabilistic method to approximate
11+pi](http://en.wikipedia.org/wiki/Approximations_of_%CF%80#Summing_a_circle.27s_area),
12+using a webservice to get random points. Finally, we will serve an HTML file
13+along with a couple of Javascript files, among which one that's automatically
14+generated from the API type and which will provide ready-to-use functions to
15+query your API.
16+
17+The source for this tutorial section is a literate haskell file, so first we
18+need to have some language extensions and imports:
19+
20+``` haskell
21+{-# LANGUAGE DataKinds #-}
22+{-# LANGUAGE DeriveGeneric #-}
23+{-# LANGUAGE OverloadedStrings #-}
24+{-# LANGUAGE TypeOperators #-}
25+
26+module Javascript where
27+
28+import Control.Monad.IO.Class
29+import Data.Aeson
30+import Data.Proxy
31+import Data.Text as T (Text)
32+import Data.Text.IO as T (writeFile, readFile)
33+import GHC.Generics
34+import Language.Javascript.JQuery
35+import Network.Wai
36+import Network.Wai.Handler.Warp
37+import qualified Data.Text as T
38+import Servant
39+import Servant.JS
40+import System.Random
41+```
42+
43+Now let's have the API type(s) and the accompanying datatypes.
44+
45+``` haskell
46+type API = "point" :> Get '[JSON] Point
47+ :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
48+
49+type API' = API :<|> Raw
50+
51+data Point = Point
52+ { x :: Double
53+ , y :: Double
54+ } deriving Generic
55+
56+instance ToJSON Point
57+
58+data Search a = Search
59+ { query :: Text
60+ , results :: [a]
61+ } deriving Generic
62+
63+mkSearch :: Text -> [a] -> Search a
64+mkSearch = Search
65+
66+instance ToJSON a => ToJSON (Search a)
67+
68+data Book = Book
69+ { author :: Text
70+ , title :: Text
71+ , year :: Int
72+ } deriving Generic
73+
74+instance ToJSON Book
75+
76+book :: Text -> Text -> Int -> Book
77+book = Book
78+```
79+
80+We need a "book database". For the purpose of this guide, let's restrict
81+ourselves to the following books.
82+
83+``` haskell
84+books :: [Book]
85+books =
86+ [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
87+ , book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008
88+ , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
89+ , book "Graham Hutton" "Programming in Haskell" 2007
90+ , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
91+ , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
92+ ]
93+```
94+
95+Now, given an optional search string `q`, we want to perform a case insensitive
96+search in that list of books. We're obviously not going to try and implement
97+the best possible algorithm, this is out of scope for this tutorial. The
98+following simple linear scan will do, given how small our list is.
99+
100+``` haskell
101+searchBook :: Monad m => Maybe Text -> m (Search Book)
102+searchBook Nothing = return (mkSearch "" books)
103+searchBook (Just q) = return (mkSearch q books')
104+
105+ where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
106+ || q' `T.isInfixOf` T.toLower (title b)
107+ )
108+ books
109+ q' = T.toLower q
110+```
111+
112+We also need an endpoint that generates random points `(x, y)` with `-1 <= x,y
113+<= 1`. The code below uses
114+[random](http://hackage.haskell.org/package/random)'s `System.Random`.
115+
116+``` haskell
117+randomPoint :: MonadIO m => m Point
118+randomPoint = liftIO . getStdRandom $ \g ->
119+ let (rx, g') = randomR (-1, 1) g
120+ (ry, g'') = randomR (-1, 1) g'
121+ in (Point rx ry, g'')
122+```
123+
124+If we add static file serving, our server is now complete.
125+
126+``` haskell
127+api :: Proxy API
128+api = Proxy
129+
130+api' :: Proxy API'
131+api' = Proxy
132+
133+server :: Server API
134+server = randomPoint
135+ :<|> searchBook
136+
137+server' :: Server API'
138+server' = server
139+ :<|> serveDirectory "static"
140+
141+app :: Application
142+app = serve api' server'
143+
144+main :: IO ()
145+main = run 8000 app
146+```
147+
148+Why two different API types, proxies and servers though? Simply because we
149+don't want to generate javascript functions for the `Raw` part of our API type,
150+so we need a `Proxy` for our API type `API'` without its `Raw` endpoint.
151+
152+Very similarly to how one can derive haskell functions, we can derive the
153+javascript with just a simple function call to `jsForAPI` from
154+`Servant.JQuery`.
155+
156+``` haskell
157+apiJS :: Text
158+apiJS = jsForAPI api vanillaJS
159+```
160+
161+This `Text` contains 2 Javascript functions, 'getPoint' and 'getBooks':
162+
163+``` javascript
164+var getPoint = function(onSuccess, onError)
165+{
166+ var xhr = new XMLHttpRequest();
167+ xhr.open('GET', '/point', true);
168+ xhr.setRequestHeader("Accept","application/json");
169+ xhr.onreadystatechange = function (e) {
170+ if (xhr.readyState == 4) {
171+ if (xhr.status == 204 || xhr.status == 205) {
172+ onSuccess();
173+ } else if (xhr.status >= 200 && xhr.status < 300) {
174+ var value = JSON.parse(xhr.responseText);
175+ onSuccess(value);
176+ } else {
177+ var value = JSON.parse(xhr.responseText);
178+ onError(value);
179+ }
180+ }
181+ }
182+ xhr.send(null);
183+}
184+
185+var getBooks = function(q, onSuccess, onError)
186+{
187+ var xhr = new XMLHttpRequest();
188+ xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true);
189+ xhr.setRequestHeader("Accept","application/json");
190+ xhr.onreadystatechange = function (e) {
191+ if (xhr.readyState == 4) {
192+ if (xhr.status == 204 || xhr.status == 205) {
193+ onSuccess();
194+ } else if (xhr.status >= 200 && xhr.status < 300) {
195+ var value = JSON.parse(xhr.responseText);
196+ onSuccess(value);
197+ } else {
198+ var value = JSON.parse(xhr.responseText);
199+ onError(value);
200+ }
201+ }
202+ }
203+ xhr.send(null);
204+}
205+```
206+
207+We created a directory `static` that contains two static files: `index.html`,
208+which is the entrypoint to our little web application; and `ui.js`, which
209+contains some hand-written javascript. This javascript code assumes the two
210+generated functions `getPoint` and `getBooks` in scope. Therefore we need to
211+write the generated javascript into a file:
212+
213+``` haskell
214+writeJSFiles :: IO ()
215+writeJSFiles = do
216+ T.writeFile "static/api.js" apiJS
217+ jq <- T.readFile =<< Language.Javascript.JQuery.file
218+ T.writeFile "static/jq.js" jq
219+```
220+
221+(We're also writing the jquery library into a file, as it's also used by
222+`ui.js`.) `static/api.js` will be included in `index.html` and the two
223+generated functions will therefore be available in `ui.js`.
224+
225+And we're good to go. You can start the `main` function of this file and go to
226+`http://localhost:8000/`. Start typing in the name of one of the authors in our
227+database or part of a book title, and check out how long it takes to
228+approximate pi using the method mentioned above.
servant-examples/LICENSE → doc/tutorial/LICENSErenamed+2−2
@@ -1,4 +1,4 @@
1−Copyright (c) 2015-2016, Servant Contributors
1+Copyright (c) 2016, Servant Contributors
22
33All rights reserved.
44
@@ -13,7 +13,7 @@ modification, are permitted provided that the following conditions are met:
1313 disclaimer in the documentation and/or other materials provided
1414 with the distribution.
1515
16− * Neither the name of Alp Mestanogullari nor the names of other
16+ * Neither the name of Servant Contributors nor the names of other
1717 contributors may be used to endorse or promote products derived
1818 from this software without specific prior written permission.
1919
doc/tutorial/Server.lhsnew file+1115
@@ -0,0 +1,1115 @@
1+# Serving an API
2+
3+Enough chit-chat about type-level combinators and representing an API as a
4+type. Can we have a webservice already?
5+
6+## A first example
7+
8+Equipped with some basic knowledge about the way we represent APIs, let's now
9+write our first webservice.
10+
11+The source for this tutorial section is a literate haskell file, so first we
12+need to have some language extensions and imports:
13+
14+``` haskell
15+{-# LANGUAGE DataKinds #-}
16+{-# LANGUAGE DeriveGeneric #-}
17+{-# LANGUAGE FlexibleInstances #-}
18+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
19+{-# LANGUAGE MultiParamTypeClasses #-}
20+{-# LANGUAGE OverloadedStrings #-}
21+{-# LANGUAGE ScopedTypeVariables #-}
22+{-# LANGUAGE TypeOperators #-}
23+
24+module Server where
25+
26+import Prelude ()
27+import Prelude.Compat
28+
29+import Control.Monad.Except
30+import Control.Monad.Reader
31+import Data.Aeson.Compat
32+import Data.Aeson.Types
33+import Data.Attoparsec.ByteString
34+import Data.ByteString (ByteString)
35+import Data.List
36+import Data.Maybe
37+import Data.String.Conversions
38+import Data.Time.Calendar
39+import GHC.Generics
40+import Lucid
41+import Network.HTTP.Media ((//), (/:))
42+import Network.Wai
43+import Network.Wai.Handler.Warp
44+import Servant
45+import System.Directory
46+import Text.Blaze
47+import Text.Blaze.Html.Renderer.Utf8
48+import qualified Data.Aeson.Parser
49+import qualified Text.Blaze.Html
50+```
51+
52+**Important**: the `Servant` module comes from the **servant-server** package,
53+the one that lets us run webservers that implement a particular API type. It
54+reexports all the types from the **servant** package that let you declare API
55+types as well as everything you need to turn your request handlers into a
56+fully-fledged webserver. This means that in your applications, you can just add
57+**servant-server** as a dependency, import `Servant` and not worry about anything
58+else.
59+
60+We will write a server that will serve the following API.
61+
62+``` haskell
63+type UserAPI1 = "users" :> Get '[JSON] [User]
64+```
65+
66+Here's what we would like to see when making a GET request to `/users`.
67+
68+``` javascript
69+[ {"name": "Isaac Newton", "age": 372, "email": "isaac@newton.co.uk", "registration_date": "1683-03-01"}
70+, {"name": "Albert Einstein", "age": 136, "email": "ae@mc2.org", "registration_date": "1905-12-01"}
71+]
72+```
73+
74+Now let's define our `User` data type and write some instances for it.
75+
76+``` haskell
77+data User = User
78+ { name :: String
79+ , age :: Int
80+ , email :: String
81+ , registration_date :: Day
82+ } deriving (Eq, Show, Generic)
83+
84+instance ToJSON User
85+```
86+
87+Nothing funny going on here. But we now can define our list of two users.
88+
89+``` haskell
90+users1 :: [User]
91+users1 =
92+ [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
93+ , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
94+ ]
95+```
96+
97+Let's also write our API type.
98+
99+``` haskell ignore
100+type UserAPI1 = "users" :> Get '[JSON] [User]
101+```
102+
103+We can now take care of writing the actual webservice that will handle requests
104+to such an API. This one will be very simple, being reduced to just a single
105+endpoint. The type of the web application is determined by the API type,
106+through a *type family* named `Server`. (Type families are just functions that
107+take types as input and return types.) The `Server` type family will compute
108+the right type that a bunch of request handlers should have just from the
109+corresponding API type.
110+
111+The first thing to know about the `Server` type family is that behind the
112+scenes it will drive the routing, letting you focus only on the business
113+logic. The second thing to know is that for each endpoint, your handlers will
114+by default run in the `ExceptT ServantErr IO` monad. This is overridable very
115+easily, as explained near the end of this guide. Third thing, the type of the
116+value returned in that monad must be the same as the second argument of the
117+HTTP method combinator used for the corresponding endpoint. In our case, it
118+means we must provide a handler of type `ExceptT ServantErr IO [User]`. Well,
119+we have a monad, let's just `return` our list:
120+
121+``` haskell
122+server1 :: Server UserAPI1
123+server1 = return users1
124+```
125+
126+That's it. Now we can turn `server` into an actual webserver using
127+[wai](http://hackage.haskell.org/package/wai) and
128+[warp](http://hackage.haskell.org/package/warp):
129+
130+``` haskell
131+userAPI :: Proxy UserAPI1
132+userAPI = Proxy
133+
134+-- 'serve' comes from servant and hands you a WAI Application,
135+-- which you can think of as an "abstract" web application,
136+-- not yet a webserver.
137+app1 :: Application
138+app1 = serve userAPI server1
139+```
140+
141+The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
142+But that's about as much boilerplate as you get.
143+
144+And we're done! Let's run our webservice on the port 8081.
145+
146+``` haskell
147+main :: IO ()
148+main = run 8081 app1
149+```
150+
151+You can put this all into a file or just grab [servant's
152+repo](http://github.com/haskell-servant/servant) and look at the
153+*doc/tutorial* directory. This code (the source of this web page) is in
154+*doc/tutorial/Server.lhs*.
155+
156+If you run it, you can go to `http://localhost:8081/users` in your browser or
157+query it with curl and you see:
158+
159+``` bash
160+$ curl http://localhost:8081/users
161+[{"email":"isaac@newton.co.uk","registration_date":"1683-03-01","age":372,"name":"Isaac Newton"},{"email":"ae@mc2.org","registration_date":"1905-12-01","age":136,"name":"Albert Einstein"}]
162+```
163+
164+## More endpoints
165+
166+What if we want more than one endpoint? Let's add `/albert` and `/isaac` to
167+view the corresponding users encoded in JSON.
168+
169+``` haskell
170+type UserAPI2 = "users" :> Get '[JSON] [User]
171+ :<|> "albert" :> Get '[JSON] User
172+ :<|> "isaac" :> Get '[JSON] User
173+```
174+
175+And let's adapt our code a bit.
176+
177+``` haskell
178+isaac :: User
179+isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
180+
181+albert :: User
182+albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
183+
184+users2 :: [User]
185+users2 = [isaac, albert]
186+```
187+
188+Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
189+are going to separate the handlers with `:<|>` too! They must be provided in
190+the same order as in in the API type.
191+
192+``` haskell
193+server2 :: Server UserAPI2
194+server2 = return users2
195+ :<|> return albert
196+ :<|> return isaac
197+```
198+
199+And that's it! You can run this example in the same way that we showed for
200+`server1` and check out the data available at `/users`, `/albert` and `/isaac`.
201+
202+## From combinators to handler arguments
203+
204+Fine, we can write trivial webservices easily, but none of the two above use
205+any "fancy" combinator from servant. Let's address this and use `QueryParam`,
206+`Capture` and `ReqBody` right away. You'll see how each occurence of these
207+combinators in an endpoint makes the corresponding handler receive an
208+argument of the appropriate type automatically. You don't have to worry about
209+manually looking up URL captures or query string parameters, or
210+decoding/encoding data from/to JSON. Never.
211+
212+We are going to use the following data types and functions to implement a
213+server for `API`.
214+
215+``` haskell
216+type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
217+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
218+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
219+
220+data Position = Position
221+ { xCoord :: Int
222+ , yCoord :: Int
223+ } deriving Generic
224+
225+instance ToJSON Position
226+
227+newtype HelloMessage = HelloMessage { msg :: String }
228+ deriving Generic
229+
230+instance ToJSON HelloMessage
231+
232+data ClientInfo = ClientInfo
233+ { clientName :: String
234+ , clientEmail :: String
235+ , clientAge :: Int
236+ , clientInterestedIn :: [String]
237+ } deriving Generic
238+
239+instance FromJSON ClientInfo
240+instance ToJSON ClientInfo
241+
242+data Email = Email
243+ { from :: String
244+ , to :: String
245+ , subject :: String
246+ , body :: String
247+ } deriving Generic
248+
249+instance ToJSON Email
250+
251+emailForClient :: ClientInfo -> Email
252+emailForClient c = Email from' to' subject' body'
253+
254+ where from' = "great@company.com"
255+ to' = clientEmail c
256+ subject' = "Hey " ++ clientName c ++ ", we miss you!"
257+ body' = "Hi " ++ clientName c ++ ",\n\n"
258+ ++ "Since you've recently turned " ++ show (clientAge c)
259+ ++ ", have you checked out our latest "
260+ ++ intercalate ", " (clientInterestedIn c)
261+ ++ " products? Give us a visit!"
262+```
263+
264+We can implement handlers for the three endpoints:
265+
266+``` haskell
267+server3 :: Server API
268+server3 = position
269+ :<|> hello
270+ :<|> marketing
271+
272+ where position :: Int -> Int -> ExceptT ServantErr IO Position
273+ position x y = return (Position x y)
274+
275+ hello :: Maybe String -> ExceptT ServantErr IO HelloMessage
276+ hello mname = return . HelloMessage $ case mname of
277+ Nothing -> "Hello, anonymous coward"
278+ Just n -> "Hello, " ++ n
279+
280+ marketing :: ClientInfo -> ExceptT ServantErr IO Email
281+ marketing clientinfo = return (emailForClient clientinfo)
282+```
283+
284+Did you see that? The types for your handlers changed to be just what we
285+needed! In particular:
286+
287+ - a `Capture "something" a` becomes an argument of type `a` (for `position`);
288+ - a `QueryParam "something" a` becomes an argument of type `Maybe a` (because
289+an endpoint can technically be accessed without specifying any query
290+string parameter, we decided to "force" handlers to be aware that the
291+parameter might not always be there);
292+
293+ - a `ReqBody contentTypeList a` becomes an argument of type `a`;
294+
295+And that's it. Here's the example in action:
296+
297+``` bash
298+$ curl http://localhost:8081/position/1/2
299+{"xCoord":1,"yCoord":2}
300+$ curl http://localhost:8081/hello
301+{"msg":"Hello, anonymous coward"}
302+$ curl http://localhost:8081/hello?name=Alp
303+{"msg":"Hello, Alp"}
304+$ curl -X POST -d '{"clientName":"Alp Mestanogullari", "clientEmail" : "alp@foo.com", "clientAge": 25, "clientInterestedIn": ["haskell", "mathematics"]}' -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8081/marketing
305+{"subject":"Hey Alp Mestanogullari, we miss you!","body":"Hi Alp Mestanogullari,\n\nSince you've recently turned 25, have you checked out our latest haskell, mathematics products? Give us a visit!","to":"alp@foo.com","from":"great@company.com"}
306+```
307+
308+For reference, here's a list of some combinators from **servant**:
309+
310+ > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `ExceptT ServantErr IO <something>`.
311+ > - `Capture "something" a` becomes an argument of type `a`.
312+ > - `QueryParam "something" a`, `Header "something" a` all become arguments of type `Maybe a`, because there might be no value at all specified by the client for these.
313+ > - `QueryFlag "something"` gets turned into an argument of type `Bool`.
314+ > - `QueryParams "something" a` gets turned into an argument of type `[a]`.
315+ > - `ReqBody contentTypes a` gets turned into an argument of type `a`.
316+
317+## The `FromHttpApiData`/`ToHttpApiData` classes
318+
319+Wait... How does **servant** know how to decode the `Int`s from the URL? Or how
320+to decode a `ClientInfo` value from the request body? This is what this and the
321+following two sections address.
322+
323+`Capture`s and `QueryParam`s are represented by some textual value in URLs.
324+`Header`s are similarly represented by a pair of a header name and a
325+corresponding (textual) value in the request's "metadata". How types are
326+decoded from headers, captures, and query params is expressed in a class
327+`FromHttpApiData` (from the package
328+[**http-api-data**](http://hackage.haskell.org/package/http-api-data)):
329+
330+``` haskell ignore
331+class FromHttpApiData a where
332+ {-# MINIMAL parseUrlPiece | parseQueryParam #-}
333+ -- | Parse URL path piece.
334+ parseUrlPiece :: Text -> Either Text a
335+ parseUrlPiece = parseQueryParam
336+
337+ -- | Parse HTTP header value.
338+ parseHeader :: ByteString -> Either Text a
339+ parseHeader = parseUrlPiece . decodeUtf8
340+
341+ -- | Parse query param value.
342+ parseQueryParam :: Text -> Either Text a
343+ parseQueryParam = parseUrlPiece
344+```
345+
346+As you can see, as long as you provide either `parseUrlPiece` (for `Capture`s)
347+or `parseQueryParam` (for `QueryParam`s), the other methods will be defined in
348+terms of this.
349+
350+**http-api-data** provides a decent number of instances, helpers for defining new
351+ones, and wonderful documentation.
352+
353+There's not much else to say about these classes. You will need instances for
354+them when using `Capture`, `QueryParam`, `QueryParams`, and `Header` with your
355+types. You will need `FromHttpApiData` instances for server-side request
356+handlers and `ToHttpApiData` instances only when using
357+**servant-client**, as described in the [section about deriving haskell
358+functions to query an API](Client.html).
359+
360+## Using content-types with your data types
361+
362+The same principle was operating when decoding request bodies from JSON, and
363+responses *into* JSON. (JSON is just the running example - you can do this with
364+any content-type.)
365+
366+This section introduces a couple of typeclasses provided by **servant** that make
367+all of this work.
368+
369+### The truth behind `JSON`
370+
371+
372+What exactly is `JSON` (the type as used in `Get '[JSON] User`)? Like the 3
373+other content-types provided out of the box by **servant**, it's a really dumb
374+data type.
375+
376+``` haskell ignore
377+data JSON
378+data PlainText
379+data FormUrlEncoded
380+data OctetStream
381+```
382+
383+Obviously, this is not all there is to `JSON`, otherwise it would be quite
384+pointless. Like most of the data types in **servant**, `JSON` is mostly there as
385+a special *symbol* that's associated with encoding (resp. decoding) to (resp.
386+from) the *JSON* format. The way this association is performed can be
387+decomposed into two steps.
388+
389+The first step is to provide a proper
390+`MediaType` (from
391+[**http-media**](https://hackage.haskell.org/package/http-media-0.6.2/docs/Network-HTTP-Media.html))
392+representation for `JSON`, or for your own content-types. If you look at the
393+haddocks from this link, you can see that we just have to specify
394+`application/json` using the appropriate functions. In our case, we can just
395+use `(//) :: ByteString -> ByteString -> MediaType`. The precise way to specify
396+the `MediaType` is to write an instance for the `Accept` class:
397+
398+``` haskell ignore
399+-- for reference:
400+class Accept ctype where
401+ contentType :: Proxy ctype -> MediaType
402+
403+instance Accept JSON where
404+ contentType _ = "application" // "json"
405+```
406+
407+The second step is centered around the `MimeRender` and `MimeUnrender` classes.
408+These classes just let you specify a way to encode and decode
409+values into or from your content-type's representation.
410+
411+``` haskell ignore
412+class Accept ctype => MimeRender ctype a where
413+ mimeRender :: Proxy ctype -> a -> ByteString
414+ -- alternatively readable as:
415+ mimeRender :: Proxy ctype -> (a -> ByteString)
416+```
417+
418+Given a content-type and some user type, `MimeRender` provides a function that
419+encodes values of type `a` to lazy `ByteString`s.
420+
421+In the case of `JSON`, this is easily dealt with! For any type `a` with a
422+`ToJSON` instance, we can render values of that type to JSON using
423+`Data.Aeson.encode`.
424+
425+``` haskell ignore
426+instance ToJSON a => MimeRender JSON a where
427+ mimeRender _ = encode
428+```
429+
430+And now the `MimeUnrender` class, which lets us extract values from lazy
431+`ByteString`s, alternatively failing with an error string.
432+
433+``` haskell ignore
434+class Accept ctype => MimeUnrender ctype a where
435+ mimeUnrender :: Proxy ctype -> ByteString -> Either String a
436+```
437+
438+We don't have much work to do there either, `Data.Aeson.eitherDecode` is
439+precisely what we need. However, it only allows arrays and objects as toplevel
440+JSON values and this has proven to get in our way more than help us so we wrote
441+our own little function around **aeson** and **attoparsec** that allows any type of
442+JSON value at the toplevel of a "JSON document". Here's the definition in case
443+you are curious.
444+
445+``` haskell
446+eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
447+eitherDecodeLenient input = do
448+ v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
449+ parseEither parseJSON v
450+```
451+
452+This function is exactly what we need for our `MimeUnrender` instance.
453+
454+``` haskell ignore
455+instance FromJSON a => MimeUnrender JSON a where
456+ mimeUnrender _ = eitherDecodeLenient
457+```
458+
459+And this is all the code that lets you use `JSON` with `ReqBody`, `Get`,
460+`Post` and friends. We can check our understanding by implementing support
461+for an `HTML` content-type, so that users of your webservice can access an
462+HTML representation of the data they want, ready to be included in any HTML
463+document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/),
464+simply by adding `Accept: text/html` to their request headers.
465+
466+### Case-studies: **servant-blaze** and **servant-lucid**
467+
468+These days, most of the haskellers who write their HTML UIs directly from
469+Haskell use either [**blaze-html**](http://hackage.haskell.org/package/blaze-html)
470+or [**lucid**](http://hackage.haskell.org/package/lucid). The best option for
471+**servant** is obviously to support both (and hopefully other templating
472+solutions!). We're first going to look at **lucid**:
473+
474+``` haskell
475+data HTMLLucid
476+```
477+
478+Once again, the data type is just there as a symbol for the encoding/decoding
479+functions, except that this time we will only worry about encoding since
480+**lucid** doesn't provide a way to extract data from HTML.
481+
482+``` haskell
483+instance Accept HTMLLucid where
484+ contentType _ = "text" // "html" /: ("charset", "utf-8")
485+```
486+
487+Note that this instance uses the `(/:)` operator from **http-media** which lets
488+us specify additional information about a content-type, like the charset here.
489+
490+The rendering instances call similar functions that take
491+types with an appropriate instance to an "abstract" HTML representation and
492+then write that to a `ByteString`.
493+
494+``` haskell
495+instance ToHtml a => MimeRender HTMLLucid a where
496+ mimeRender _ = renderBS . toHtml
497+
498+-- let's also provide an instance for lucid's
499+-- 'Html' wrapper.
500+instance MimeRender HTMLLucid (Html a) where
501+ mimeRender _ = renderBS
502+```
503+
504+For **blaze-html** everything works very similarly:
505+
506+``` haskell
507+-- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
508+-- distinct. Usually you would stick to one html rendering library and then
509+-- you can go with one 'HTML' type.
510+data HTMLBlaze
511+
512+instance Accept HTMLBlaze where
513+ contentType _ = "text" // "html" /: ("charset", "utf-8")
514+
515+instance ToMarkup a => MimeRender HTMLBlaze a where
516+ mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
517+
518+-- while we're at it, just like for lucid we can
519+-- provide an instance for rendering blaze's 'Html' type
520+instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
521+ mimeRender _ = renderHtml
522+```
523+
524+Both [**servant-blaze**](http://hackage.haskell.org/package/servant-blaze) and
525+[**servant-lucid**](http://hackage.haskell.org/package/servant-lucid) let you use
526+`HTMLLucid` and `HTMLBlaze` in any content-type list as long as you provide an instance of the
527+appropriate class (`ToMarkup` for **blaze-html**, `ToHtml` for **lucid**).
528+
529+We can now write a webservice that uses **servant-lucid** to show the `HTMLLucid`
530+content-type in action. We will be serving the following API:
531+
532+``` haskell
533+type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
534+```
535+
536+where `Person` is defined as follows:
537+
538+``` haskell
539+data Person = Person
540+ { firstName :: String
541+ , lastName :: String
542+ } deriving Generic -- for the JSON instance
543+
544+instance ToJSON Person
545+```
546+
547+Now, let's teach **lucid** how to render a `Person` as a row in a table, and then
548+a list of `Person`s as a table with a row per person.
549+
550+``` haskell
551+-- HTML serialization of a single person
552+instance ToHtml Person where
553+ toHtml person =
554+ tr_ $ do
555+ td_ (toHtml $ firstName person)
556+ td_ (toHtml $ lastName person)
557+
558+ -- do not worry too much about this
559+ toHtmlRaw = toHtml
560+
561+-- HTML serialization of a list of persons
562+instance ToHtml [Person] where
563+ toHtml persons = table_ $ do
564+ tr_ $ do
565+ th_ "first name"
566+ th_ "last name"
567+
568+ -- this just calls toHtml on each person of the list
569+ -- and concatenates the resulting pieces of HTML together
570+ foldMap toHtml persons
571+
572+ toHtmlRaw = toHtml
573+```
574+
575+We create some `Person` values and serve them as a list:
576+
577+``` haskell
578+people :: [Person]
579+people =
580+ [ Person "Isaac" "Newton"
581+ , Person "Albert" "Einstein"
582+ ]
583+
584+personAPI :: Proxy PersonAPI
585+personAPI = Proxy
586+
587+server4 :: Server PersonAPI
588+server4 = return people
589+
590+app2 :: Application
591+app2 = serve personAPI server4
592+```
593+
594+And we're good to go:
595+
596+``` bash
597+$ curl http://localhost:8081/persons
598+[{"lastName":"Newton","firstName":"Isaac"},{"lastName":"Einstein","firstName":"Albert"}]
599+$ curl -H 'Accept: text/html' http://localhost:8081/persons
600+<table><tr><td>first name</td><td>last name</td></tr><tr><td>Isaac</td><td>Newton</td></tr><tr><td>Albert</td><td>Einstein</td></tr></table>
601+# or just point your browser to http://localhost:8081/persons
602+```
603+
604+## The `ExceptT ServantErr IO` monad
605+
606+At the heart of the handlers is the monad they run in, namely `ExceptT
607+ServantErr IO`
608+([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
609+One might wonder: why this monad? The answer is that it is the
610+simplest monad with the following properties:
611+
612+- it lets us both return a successful result (using `return`)
613+or "fail" with a descriptive error (using `throwError`);
614+- it lets us perform IO, which is absolutely vital since most webservices exist
615+as interfaces to databases that we interact with in `IO`.
616+
617+Let's recall some definitions.
618+
619+``` haskell ignore
620+-- from the 'mtl' package at
621+newtype ExceptT e m a = ExceptT (m (Either e a))
622+```
623+
624+In short, this means that a handler of type `ExceptT ServantErr IO a` is simply
625+equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
626+action that either returns an error or a result.
627+
628+The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)
629+from which `ExceptT` comes is worth looking at.
630+Perhaps most importantly, `ExceptT` is an instance of `MonadError`, so
631+`throwError` can be used to return an error from your handler (whereas `return`
632+ is enough to return a success).
633+
634+Most of what you'll be doing in your handlers is running some IO and,
635+depending on the result, you might sometimes want to throw an error of some
636+kind and abort early. The next two sections cover how to do just that.
637+
638+### Performing IO
639+
640+Another important instance from the list above is `MonadIO m => MonadIO
641+(ExceptT e m)`.
642+[`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html)
643+is a class from the **transformers** package defined as:
644+
645+``` haskell ignore
646+class Monad m => MonadIO m where
647+ liftIO :: IO a -> m a
648+```
649+
650+The `IO` monad provides a `MonadIO` instance. Hence for any type
651+`e`, `ExceptT e IO` has a `MonadIO` instance. So if you want to run any kind of
652+IO computation in your handlers, just use `liftIO`:
653+
654+``` haskell
655+type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
656+
657+newtype FileContent = FileContent
658+ { content :: String }
659+ deriving Generic
660+
661+instance ToJSON FileContent
662+
663+server5 :: Server IOAPI1
664+server5 = do
665+ filecontent <- liftIO (readFile "myfile.txt")
666+ return (FileContent filecontent)
667+```
668+
669+### Failing, through `ServantErr`
670+
671+If you want to explicitly fail at providing the result promised by an endpoint
672+using the appropriate HTTP status code (not found, unauthorized, etc) and some
673+error message, all you have to do is use the `throwError` function mentioned above
674+and provide it with the appropriate value of type `ServantErr`, which is
675+defined as:
676+
677+``` haskell ignore
678+data ServantErr = ServantErr
679+ { errHTTPCode :: Int
680+ , errReasonPhrase :: String
681+ , errBody :: ByteString -- lazy bytestring
682+ , errHeaders :: [Header]
683+ }
684+```
685+
686+Many standard values are provided out of the box by the `Servant.Server`
687+module. If you want to use these values but add a body or some headers, just
688+use record update syntax:
689+
690+``` haskell
691+failingHandler :: ExceptT ServantErr IO ()
692+failingHandler = throwError myerr
693+
694+ where myerr :: ServantErr
695+ myerr = err503 { errBody = "Sorry dear user." }
696+```
697+
698+Here's an example where we return a customised 404-Not-Found error message in
699+the response body if "myfile.txt" isn't there:
700+
701+``` haskell
702+server6 :: Server IOAPI1
703+server6 = do
704+ exists <- liftIO (doesFileExist "myfile.txt")
705+ if exists
706+ then liftIO (readFile "myfile.txt") >>= return . FileContent
707+ else throwError custom404Err
708+
709+ where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
710+```
711+
712+Here's how that server looks in action:
713+
714+``` bash
715+$ curl --verbose http://localhost:8081/myfile.txt
716+[snip]
717+* Connected to localhost (127.0.0.1) port 8081 (#0)
718+> GET /myfile.txt HTTP/1.1
719+> User-Agent: curl/7.30.0
720+> Host: localhost:8081
721+> Accept: */*
722+>
723+< HTTP/1.1 404 Not Found
724+[snip]
725+myfile.txt just isnt there, please leave this server alone.
726+
727+$ echo Hello > myfile.txt
728+
729+$ curl --verbose http://localhost:8081/myfile.txt
730+[snip]
731+* Connected to localhost (127.0.0.1) port 8081 (#0)
732+> GET /myfile.txt HTTP/1.1
733+> User-Agent: curl/7.30.0
734+> Host: localhost:8081
735+> Accept: */*
736+>
737+< HTTP/1.1 200 OK
738+[snip]
739+< Content-Type: application/json
740+[snip]
741+{"content":"Hello\n"}
742+```
743+
744+## Response headers
745+
746+To add headers to your response, use
747+[addHeader](http://hackage.haskell.org/package/servant/docs/Servant-API-ResponseHeaders.html).
748+Note that this changes the type of your API, as we can see in the following example:
749+
750+``` haskell
751+type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
752+
753+myHandler :: Server MyHandler
754+myHandler = return $ addHeader 1797 albert
755+```
756+
757+Note that the type of `addHeader x` is different than the type of `x`!
758+
759+## Serving static files
760+
761+**servant-server** also provides a way to just serve the content of a directory
762+under some path in your web API. As mentioned earlier in this document, the
763+`Raw` combinator can be used in your APIs to mean "plug here any WAI
764+application". Well, **servant-server** provides a function to get a file and
765+directory serving WAI application, namely:
766+
767+``` haskell ignore
768+-- exported by Servant and Servant.Server
769+serveDirectory :: FilePath -> Server Raw
770+```
771+
772+`serveDirectory`'s argument must be a path to a valid directory.
773+
774+Here's an example API that will serve some static files:
775+
776+``` haskell
777+type StaticAPI = "static" :> Raw
778+```
779+
780+And the server:
781+
782+``` haskell
783+staticAPI :: Proxy StaticAPI
784+staticAPI = Proxy
785+```
786+
787+``` haskell
788+server7 :: Server StaticAPI
789+server7 = serveDirectory "static-files"
790+
791+app3 :: Application
792+app3 = serve staticAPI server7
793+```
794+
795+This server will match any request whose path starts with `/static` and will look
796+for a file at the path described by the rest of the request path, inside the
797+ *static-files/* directory of the path you run the program from.
798+
799+In other words: If a client requests `/static/foo.txt`, the server will look for a file at
800+`./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
801+If it doesn't exist, the handler will fail with a `404` status code.
802+
803+## Nested APIs
804+
805+Let's see how you can define APIs in a modular way, while avoiding repetition.
806+Consider this simple example:
807+
808+``` haskell
809+type UserAPI3 = -- view the user with given userid, in JSON
810+ Capture "userid" Int :> Get '[JSON] User
811+
812+ :<|> -- delete the user with given userid. empty response
813+ Capture "userid" Int :> Delete '[] ()
814+```
815+
816+We can instead factor out the `userid`:
817+
818+``` haskell
819+type UserAPI4 = Capture "userid" Int :>
820+ ( Get '[JSON] User
821+ :<|> Delete '[] ()
822+ )
823+```
824+
825+However, you have to be aware that this has an effect on the type of the
826+corresponding `Server`:
827+
828+``` haskell ignore
829+Server UserAPI3 = (Int -> ExceptT ServantErr IO User)
830+ :<|> (Int -> ExceptT ServantErr IO ())
831+
832+Server UserAPI4 = Int -> ( ExceptT ServantErr IO User
833+ :<|> ExceptT ServantErr IO ()
834+ )
835+```
836+
837+In the first case, each handler receives the *userid* argument. In the latter,
838+the whole `Server` takes the *userid* and has handlers that are just
839+computations in `ExceptT`, with no arguments. In other words:
840+
841+``` haskell
842+server8 :: Server UserAPI3
843+server8 = getUser :<|> deleteUser
844+
845+ where getUser :: Int -> ExceptT ServantErr IO User
846+ getUser _userid = error "..."
847+
848+ deleteUser :: Int -> ExceptT ServantErr IO ()
849+ deleteUser _userid = error "..."
850+
851+-- notice how getUser and deleteUser
852+-- have a different type! no argument anymore,
853+-- the argument directly goes to the whole Server
854+server9 :: Server UserAPI4
855+server9 userid = getUser userid :<|> deleteUser userid
856+
857+ where getUser :: Int -> ExceptT ServantErr IO User
858+ getUser = error "..."
859+
860+ deleteUser :: Int -> ExceptT ServantErr IO ()
861+ deleteUser = error "..."
862+```
863+
864+Note that there's nothing special about `Capture` that lets you "factor it
865+out": this can be done with any combinator. Here are a few examples of APIs
866+with a combinator factored out for which we can write a perfectly valid
867+`Server`.
868+
869+``` haskell
870+-- we just factor out the "users" path fragment
871+type API1 = "users" :>
872+ ( Get '[JSON] [User] -- user listing
873+ :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
874+ )
875+
876+-- we factor out the Request Body
877+type API2 = ReqBody '[JSON] User :>
878+ ( Get '[JSON] User -- just display the same user back, don't register it
879+ :<|> Post '[JSON] () -- register the user. empty response
880+ )
881+
882+-- we factor out a Header
883+type API3 = Header "Authorization" Token :>
884+ ( Get '[JSON] SecretData -- get some secret data, if authorized
885+ :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
886+ )
887+
888+newtype Token = Token ByteString
889+newtype SecretData = SecretData ByteString
890+```
891+
892+This approach lets you define APIs modularly and assemble them all into one big
893+API type only at the end.
894+
895+``` haskell
896+type UsersAPI =
897+ Get '[JSON] [User] -- list users
898+ :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
899+ :<|> Capture "userid" Int :>
900+ ( Get '[JSON] User -- view a user
901+ :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
902+ :<|> Delete '[] () -- delete a user
903+ )
904+
905+usersServer :: Server UsersAPI
906+usersServer = getUsers :<|> newUser :<|> userOperations
907+
908+ where getUsers :: ExceptT ServantErr IO [User]
909+ getUsers = error "..."
910+
911+ newUser :: User -> ExceptT ServantErr IO ()
912+ newUser = error "..."
913+
914+ userOperations userid =
915+ viewUser userid :<|> updateUser userid :<|> deleteUser userid
916+
917+ where
918+ viewUser :: Int -> ExceptT ServantErr IO User
919+ viewUser = error "..."
920+
921+ updateUser :: Int -> User -> ExceptT ServantErr IO ()
922+ updateUser = error "..."
923+
924+ deleteUser :: Int -> ExceptT ServantErr IO ()
925+ deleteUser = error "..."
926+```
927+
928+``` haskell
929+type ProductsAPI =
930+ Get '[JSON] [Product] -- list products
931+ :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
932+ :<|> Capture "productid" Int :>
933+ ( Get '[JSON] Product -- view a product
934+ :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
935+ :<|> Delete '[] () -- delete a product
936+ )
937+
938+data Product = Product { productId :: Int }
939+
940+productsServer :: Server ProductsAPI
941+productsServer = getProducts :<|> newProduct :<|> productOperations
942+
943+ where getProducts :: ExceptT ServantErr IO [Product]
944+ getProducts = error "..."
945+
946+ newProduct :: Product -> ExceptT ServantErr IO ()
947+ newProduct = error "..."
948+
949+ productOperations productid =
950+ viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
951+
952+ where
953+ viewProduct :: Int -> ExceptT ServantErr IO Product
954+ viewProduct = error "..."
955+
956+ updateProduct :: Int -> Product -> ExceptT ServantErr IO ()
957+ updateProduct = error "..."
958+
959+ deleteProduct :: Int -> ExceptT ServantErr IO ()
960+ deleteProduct = error "..."
961+```
962+
963+``` haskell
964+type CombinedAPI = "users" :> UsersAPI
965+ :<|> "products" :> ProductsAPI
966+
967+server10 :: Server CombinedAPI
968+server10 = usersServer :<|> productsServer
969+```
970+
971+Finally, we can realize the user and product APIs are quite similar and
972+abstract that away:
973+
974+``` haskell
975+-- API for values of type 'a'
976+-- indexed by values of type 'i'
977+type APIFor a i =
978+ Get '[JSON] [a] -- list 'a's
979+ :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
980+ :<|> Capture "id" i :>
981+ ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
982+ :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
983+ :<|> Delete '[] () -- delete an 'a'
984+ )
985+
986+-- Build the appropriate 'Server'
987+-- given the handlers of the right type.
988+serverFor :: ExceptT ServantErr IO [a] -- handler for listing of 'a's
989+ -> (a -> ExceptT ServantErr IO ()) -- handler for adding an 'a'
990+ -> (i -> ExceptT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
991+ -> (i -> a -> ExceptT ServantErr IO ()) -- updating an 'a' with given id
992+ -> (i -> ExceptT ServantErr IO ()) -- deleting an 'a' given its id
993+ -> Server (APIFor a i)
994+serverFor = error "..."
995+-- implementation left as an exercise. contact us on IRC
996+-- or the mailing list if you get stuck!
997+```
998+
999+## Using another monad for your handlers
1000+
1001+Remember how `Server` turns combinators for HTTP methods into `ExceptT
1002+ServantErr IO`? Well, actually, there's more to that. `Server` is actually a
1003+simple type synonym.
1004+
1005+``` haskell ignore
1006+type Server api = ServerT api (ExceptT ServantErr IO)
1007+```
1008+
1009+`ServerT` is the actual type family that computes the required types for the
1010+handlers that's part of the `HasServer` class. It's like `Server` except that
1011+it takes another parameter which is the monad you want your handlers to run in,
1012+or more generally the return types of your handlers. This third parameter is
1013+used for specifying the return type of the handler for an endpoint, e.g when
1014+computing `ServerT (Get '[JSON] Person) SomeMonad`. The result would be
1015+`SomeMonad Person`.
1016+
1017+The first and main question one might have then is: how do we write handlers
1018+that run in another monad? How can we "bring back" the value from a given monad
1019+into something **servant** can understand?
1020+
1021+### Natural transformations
1022+
1023+If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1024+do we have?
1025+
1026+``` haskell ignore
1027+newtype m :~> n = Nat { unNat :: forall a. m a -> n a}
1028+```
1029+
1030+For example:
1031+
1032+``` haskell
1033+listToMaybeNat :: [] :~> Maybe
1034+listToMaybeNat = Nat listToMaybe -- from Data.Maybe
1035+```
1036+
1037+(`Nat` comes from "natural transformation", in case you're wondering.)
1038+
1039+So if you want to write handlers using another monad/type than `ExceptT
1040+ServantErr IO`, say the `Reader String` monad, the first thing you have to
1041+prepare is a function:
1042+
1043+``` haskell ignore
1044+readerToHandler :: Reader String :~> ExceptT ServantErr IO
1045+```
1046+
1047+Let's start with `readerToHandler'`. We obviously have to run the `Reader`
1048+computation by supplying it with a `String`, like `"hi"`. We get an `a` out
1049+from that and can then just `return` it into `ExceptT`. We can then just wrap
1050+that function with the `Nat` constructor to make it have the fancier type.
1051+
1052+``` haskell
1053+readerToHandler' :: forall a. Reader String a -> ExceptT ServantErr IO a
1054+readerToHandler' r = return (runReader r "hi")
1055+
1056+readerToHandler :: Reader String :~> ExceptT ServantErr IO
1057+readerToHandler = Nat readerToHandler'
1058+```
1059+
1060+We can write some simple webservice with the handlers running in `Reader String`.
1061+
1062+``` haskell
1063+type ReaderAPI = "a" :> Get '[JSON] Int
1064+ :<|> "b" :> Get '[JSON] String
1065+
1066+readerAPI :: Proxy ReaderAPI
1067+readerAPI = Proxy
1068+
1069+readerServerT :: ServerT ReaderAPI (Reader String)
1070+readerServerT = a :<|> b
1071+
1072+ where a :: Reader String Int
1073+ a = return 1797
1074+
1075+ b :: Reader String String
1076+ b = ask
1077+```
1078+
1079+We unfortunately can't use `readerServerT` as an argument of `serve`, because
1080+`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT
1081+ServantErr IO`. But there's a simple solution to this.
1082+
1083+### Enter `enter`
1084+
1085+That's right. We have just written `readerToHandler`, which is exactly what we
1086+would need to apply to all handlers to make the handlers have the
1087+right type for `serve`. Being cumbersome to do by hand, we provide a function
1088+`enter` which takes a natural transformation between two parametrized types `m`
1089+and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
1090+
1091+In our case, we can wrap up our little webservice by using `enter
1092+readerToHandler` on our handlers.
1093+
1094+``` haskell
1095+readerServer :: Server ReaderAPI
1096+readerServer = enter readerToHandler readerServerT
1097+
1098+app4 :: Application
1099+app4 = serve readerAPI readerServer
1100+```
1101+
1102+This is the webservice in action:
1103+
1104+``` bash
1105+$ curl http://localhost:8081/a
1106+1797
1107+$ curl http://localhost:8081/b
1108+"hi"
1109+```
1110+
1111+## Conclusion
1112+
1113+You're now equipped to write webservices/web-applications using
1114+**servant**. The rest of this document focuses on **servant-client**,
1115+**servant-js** and **servant-docs**.
doc/tutorial/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
doc/tutorial/index.rstnew file+18
@@ -0,0 +1,18 @@
1+Tutorial
2+========
3+
4+This is an introductory tutorial to **servant**.
5+
6+(Any comments, issues or feedback about the tutorial can be handled
7+through
8+`servant's issue tracker <http://github.com/haskell-servant/servant/issues>`_.)
9+
10+
11+.. toctree::
12+ :maxdepth: 1
13+
14+ ApiType.lhs
15+ Server.lhs
16+ Client.lhs
17+ Javascript.lhs
18+ Docs.lhs
servant-examples/tutorial/t9/index.html → doc/tutorial/static/index.htmlrenamed+3−3
@@ -3,11 +3,11 @@
33 <head>
44 <meta name="viewport" content="width=device-width, initial-scale=1">
55 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6− <title>Tutorial - 9 - servant-jquery</title>
6+ <title>servant-js Example</title>
77 </head>
88 <body>
99 <h1>Books</h1>
10− <input type="search" name="q" id="q" placeholder="Search author or book title..." />
10+ <input type="search" name="q" id="q" placeholder="Search author or book title..." autocomplete="off"/>
1111 <div>
1212 <p>Results for <strong id="query">""</strong></p>
1313 <ul id="results">
@@ -23,4 +23,4 @@
2323 <script type="text/javascript" src="/api.js"></script>
2424 <script type="text/javascript" src="/ui.js"></script>
2525
26− </body>
26+ </body>
servant-examples/tutorial/t9/ui.js → doc/tutorial/static/ui.jsrenamed−1
@@ -58,4 +58,3 @@ function refresh()
5858}
5959
6060window.setInterval(refresh, 200);
61−
doc/tutorial/test/JavascriptSpec.hsnew file+32
@@ -0,0 +1,32 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module JavascriptSpec where
4+
5+import Data.List
6+import Data.String
7+import Data.String.Conversions
8+import Test.Hspec
9+import Test.Hspec.Wai
10+
11+import Javascript
12+
13+spec :: Spec
14+spec = do
15+ describe "apiJS" $ do
16+ it "is contained verbatim in Javascript.lhs" $ do
17+ code <- readFile "Javascript.lhs"
18+ cs apiJS `shouldSatisfy` (`isInfixOf` code)
19+
20+ describe "writeJSFiles" $ do
21+ it "[not a test] write apiJS to static/api.js" $ do
22+ writeJSFiles
23+
24+ describe "app" $ with (return app) $ do
25+ context "/api.js" $ do
26+ it "delivers apiJS" $ do
27+ get "/api.js" `shouldRespondWith` (fromString (cs apiJS))
28+
29+ context "/" $ do
30+ it "delivers something" $ do
31+ get "" `shouldRespondWith` 200
32+ get "/" `shouldRespondWith` 200
doc/tutorial/test/Spec.hsnew file+1
@@ -0,0 +1 @@
1+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
doc/tutorial/tinc.yamlnew file+13
@@ -0,0 +1,13 @@
1+dependencies:
2+ - name: servant
3+ path: ../../servant
4+ - name: servant-server
5+ path: ../../servant-server
6+ - name: servant-client
7+ path: ../../servant-client
8+ - name: servant-js
9+ path: ../../servant-js
10+ - name: servant-docs
11+ path: ../../servant-docs
12+ - name: servant-foreign
13+ path: ../../servant-foreign
doc/tutorial/tutorial.cabalnew file+63
@@ -0,0 +1,63 @@
1+name: tutorial
2+version: 0.5
3+synopsis: The servant tutorial
4+homepage: http://haskell-servant.github.io/
5+license: BSD3
6+license-file: LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+library
13+ exposed-modules: ApiType
14+ , Client
15+ , Docs
16+ , Javascript
17+ , Server
18+ build-depends: base == 4.*
19+ , base-compat
20+ , text
21+ , aeson
22+ , aeson-compat
23+ , blaze-html
24+ , directory
25+ , blaze-markup
26+ , servant == 0.5.*
27+ , servant-server == 0.5.*
28+ , servant-client == 0.5.*
29+ , servant-docs == 0.5.*
30+ , servant-js == 0.5.*
31+ , warp
32+ , http-media
33+ , lucid
34+ , time
35+ , string-conversions
36+ , bytestring
37+ , attoparsec
38+ , mtl
39+ , random
40+ , js-jquery
41+ , wai
42+ , http-types
43+ , transformers
44+ , markdown-unlit >= 0.4
45+ , http-client
46+ default-language: Haskell2010
47+ ghc-options: -Wall -Werror -pgmL markdown-unlit
48+ -- to silence aeson-0.10 warnings:
49+ ghc-options: -fno-warn-missing-methods
50+ ghc-options: -fno-warn-name-shadowing
51+
52+test-suite spec
53+ type: exitcode-stdio-1.0
54+ ghc-options:
55+ -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures
56+ default-language: Haskell2010
57+ hs-source-dirs: test
58+ main-is: Spec.hs
59+ build-depends: base == 4.*
60+ , tutorial
61+ , hspec
62+ , hspec-wai
63+ , string-conversions
servant-examples/Setup.hsdeleted−2
@@ -1,2 +0,0 @@
1−import Distribution.Simple
2−main = defaultMain
servant-examples/auth-combinator/auth-combinator.hsdeleted−124
@@ -1,124 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE FlexibleInstances #-}
5−{-# LANGUAGE MultiParamTypeClasses #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
8−{-# LANGUAGE TypeFamilies #-}
9−{-# LANGUAGE TypeOperators #-}
10−{-# LANGUAGE UndecidableInstances #-}
11−
12−import Control.Monad.Trans.Except (ExceptT, throwE)
13−import Data.Aeson hiding ((.:))
14−import Data.ByteString (ByteString)
15−import Data.Monoid ((<>))
16−import Data.Map (Map, fromList)
17−import qualified Data.Map as Map
18−import Data.Text (Text)
19−import GHC.Generics
20−import Network.Wai
21−import Network.Wai.Handler.Warp
22−import Servant
23−import Servant.Server.Experimental.Auth
24−
25−-- | This file contains an authenticated server using servant's generalized
26−-- authentication support. Our basic authentication scheme is trivial: we
27−-- look for a cookie named "servant-auth-cookie" and its value will contain
28−-- a key, which we use to lookup a User. Obviously this is an absurd example,
29−-- but we pick something simple and non-standard to show you how to extend
30−-- servant's support for authentication.
31−
32−-- | A user type that we "fetch from the database" after
33−-- performing authentication
34−newtype User = User { unUser :: Text }
35−
36−-- | A (pure) database mapping keys to users.
37−database :: Map ByteString User
38−database = fromList [ ("key1", User "Anne Briggs")
39− , ("key2", User "Bruce Cockburn")
40− , ("key3", User "Ghédalia Tazartès")
41− ]
42−
43−-- | A method that, when given a password, will return a User.
44−-- This is our bespoke (and bad) authentication logic.
45−lookupUser :: ByteString -> ExceptT ServantErr IO User
46−lookupUser key = case Map.lookup key database of
47− Nothing -> throwE (err403 { errBody = "Invalid Cookie" })
48− Just usr -> return usr
49−
50−-- | The auth handler wraps a function from Request -> ExceptT ServantErr IO User
51−-- we look for a Cookie and pass the value of the cookie to `lookupUser`.
52−authHandler :: AuthHandler Request User
53−authHandler =
54− let handler req = case lookup "servant-auth-cookie" (requestHeaders req) of
55− Nothing -> throwE (err401 { errBody = "Missing auth header" })
56− Just authCookieKey -> lookupUser authCookieKey
57− in mkAuthHandler handler
58−
59−-- | Data types that will be returned from various api endpoints
60−newtype PrivateData = PrivateData { ssshhh :: Text }
61− deriving (Eq, Show, Generic)
62−
63−instance ToJSON PrivateData
64−
65−newtype PublicData = PublicData { somedata :: Text }
66− deriving (Eq, Show, Generic)
67−
68−instance ToJSON PublicData
69−
70−-- | Our private API that we want to be auth-protected.
71−type PrivateAPI = Get '[JSON] [PrivateData]
72−
73−-- | Our public API that doesn't have any protection
74−type PublicAPI = Get '[JSON] [PublicData]
75−
76−-- | Our API, with auth-protection
77−type API = "private" :> AuthProtect "cookie-auth" :> PrivateAPI
78− :<|> "public" :> PublicAPI
79−
80−-- | A value holding our type-level API
81−api :: Proxy API
82−api = Proxy
83−
84−-- | We need to specify the data returned after authentication
85−type instance AuthServerData (AuthProtect "cookie-auth") = User
86−
87−-- | The context that will be made available to request handlers. We supply the
88−-- "cookie-auth"-tagged request handler defined above, so that the 'HasServer' instance
89−-- of 'AuthProtect' can extract the handler and run it on the request.
90−serverContext :: Context (AuthHandler Request User ': '[])
91−serverContext = authHandler :. EmptyContext
92−
93−-- | Our API, where we provide all the author-supplied handlers for each end
94−-- point. Note that 'privateDataFunc' is a function that takes 'User' as an
95−-- argument. We dont' worry about the authentication instrumentation here,
96−-- that is taken care of by supplying context
97−server :: Server API
98−server = privateDataFunc :<|> return publicData
99−
100− where privateDataFunc (User name) =
101− return [PrivateData ("this is a secret: " <> name)]
102− publicData = [PublicData "this is a public piece of data"]
103−
104−-- | run our server
105−main :: IO ()
106−main = run 8080 (serveWithContext api serverContext server)
107−
108−{- Sample Session:
109−
110−$ curl -XGET localhost:8080/private
111−Missing auth header
112−>>>>>>> modify auth-combinator example for gen auth
113−>>>>>>> 8246c1f... modify auth-combinator example for gen auth
114−
115−$ curl -XGET localhost:8080/private -H "servant-auth-cookie: key3"
116−[{"ssshhh":"this is a secret: Ghédalia Tazartès"}]
117−
118−$ curl -XGET localhost:8080/private -H "servant-auth-cookie: bad-key"
119−Invalid Cookie
120−
121−$ curl -XGET localhost:8080/public
122−[{"somedata":"this is a public piece of data"}]
123−-}
124−
servant-examples/basic-auth/basic-auth.hsdeleted−105
@@ -1,105 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−{-# LANGUAGE TypeOperators #-}
6−
7−module Main where
8−
9−import Data.Aeson (ToJSON)
10−import Data.Proxy (Proxy (Proxy))
11−import Data.Text (Text)
12−import GHC.Generics (Generic)
13−import Network.Wai.Handler.Warp (run)
14−import Servant.API ((:<|>) ((:<|>)), (:>), BasicAuth,
15− Get, JSON)
16−import Servant.API.BasicAuth (BasicAuthData (BasicAuthData))
17−import Servant.Server (BasicAuthCheck (BasicAuthCheck),
18− BasicAuthResult( Authorized
19− , Unauthorized
20− ),
21− Context ((:.), EmptyContext), Server,
22− serveWithContext)
23−
24−-- | let's define some types that our API returns.
25−
26−-- | private data that needs protection
27−newtype PrivateData = PrivateData { ssshhh :: Text }
28− deriving (Eq, Show, Generic)
29−
30−instance ToJSON PrivateData
31−
32−-- | public data that anyone can use.
33−newtype PublicData = PublicData { somedata :: Text }
34− deriving (Eq, Show, Generic)
35−
36−instance ToJSON PublicData
37−
38−-- | A user we'll grab from the database when we authenticate someone
39−newtype User = User { userName :: Text }
40− deriving (Eq, Show)
41−
42−-- | a type to wrap our public api
43−type PublicAPI = Get '[JSON] [PublicData]
44−
45−-- | a type to wrap our private api
46−type PrivateAPI = Get '[JSON] PrivateData
47−
48−-- | our API
49−type API = "public" :> PublicAPI
50− :<|> "private" :> BasicAuth "foo-realm" User :> PrivateAPI
51−
52−-- | a value holding a proxy of our API type
53−api :: Proxy API
54−api = Proxy
55−
56−-- | 'BasicAuthCheck' holds the handler we'll use to verify a username and password.
57−authCheck :: BasicAuthCheck User
58−authCheck =
59− let check (BasicAuthData username password) =
60− if username == "servant" && password == "server"
61− then return (Authorized (User "servant"))
62− else return Unauthorized
63− in BasicAuthCheck check
64−
65−-- | We need to supply our handlers with the right Context. In this case,
66−-- Basic Authentication requires a Context Entry with the 'BasicAuthCheck' value
67−-- tagged with "foo-tag" This context is then supplied to 'server' and threaded
68−-- to the BasicAuth HasServer handlers.
69−serverContext :: Context (BasicAuthCheck User ': '[])
70−serverContext = authCheck :. EmptyContext
71−
72−-- | an implementation of our server. Here is where we pass all the handlers to our endpoints.
73−-- In particular, for the BasicAuth protected handler, we need to supply a function
74−-- that takes 'User' as an argument.
75−server :: Server API
76−server =
77− let publicAPIHandler = return [PublicData "foo", PublicData "bar"]
78− privateAPIHandler (user :: User) = return (PrivateData (userName user))
79− in publicAPIHandler :<|> privateAPIHandler
80−
81−-- | hello, server!
82−main :: IO ()
83−main = run 8080 (serveWithContext api serverContext server)
84−
85−{- Sample session
86−
87−$ curl -XGET localhost:8080/public
88−[{"somedata":"foo"},{"somedata":"bar"}
89−
90−$ curl -iXGET localhost:8080/private
91−HTTP/1.1 401 Unauthorized
92−transfer-encoding: chunked
93−Date: Thu, 07 Jan 2016 22:36:38 GMT
94−Server: Warp/3.1.8
95−WWW-Authenticate: Basic realm="foo-realm"
96−
97−$ curl -iXGET localhost:8080/private -H "Authorization: Basic c2VydmFudDpzZXJ2ZXI="
98−HTTP/1.1 200 OK
99−transfer-encoding: chunked
100−Date: Thu, 07 Jan 2016 22:37:58 GMT
101−Server: Warp/3.1.8
102−Content-Type: application/json
103−
104−{"ssshhh":"servant"}
105−-}
servant-examples/hackage/hackage.hsdeleted−90
@@ -1,90 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE TypeOperators #-}
5−{-# OPTIONS_GHC -fno-warn-unused-imports #-}
6−import Control.Applicative
7−import Control.Monad
8−import Control.Monad.IO.Class
9−import Control.Monad.Trans.Except
10−import Data.Aeson
11−import Data.Monoid
12−import Data.Proxy
13−import Data.Text (Text)
14−import GHC.Generics
15−import Network.HTTP.Client (Manager, defaultManagerSettings,
16− newManager)
17−import System.IO.Unsafe (unsafePerformIO)
18−import Servant.API
19−import Servant.Client
20−
21−import qualified Data.Text as T
22−import qualified Data.Text.IO as T
23−
24−type HackageAPI =
25− "users" :> Get '[JSON] [UserSummary]
26− :<|> "user" :> Capture "username" Username :> Get '[JSON] UserDetailed
27− :<|> "packages" :> Get '[JSON] [Package]
28−
29−type Username = Text
30−
31−data UserSummary = UserSummary
32− { summaryUsername :: Username
33− , summaryUserid :: Int
34− } deriving (Eq, Show)
35−
36−instance FromJSON UserSummary where
37− parseJSON (Object o) =
38− UserSummary <$> o .: "username"
39− <*> o .: "userid"
40−
41− parseJSON _ = mzero
42−
43−type Group = Text
44−
45−data UserDetailed = UserDetailed
46− { username :: Username
47− , userid :: Int
48− , groups :: [Group]
49− } deriving (Eq, Show, Generic)
50−
51−instance FromJSON UserDetailed
52−
53−newtype Package = Package { packageName :: Text }
54− deriving (Eq, Show, Generic)
55−
56−instance FromJSON Package
57−
58−hackageAPI :: Proxy HackageAPI
59−hackageAPI = Proxy
60−
61−
62−{-# NOINLINE manager #-}
63−manager :: Manager
64−manager = unsafePerformIO $ newManager defaultManagerSettings
65−
66−getUsers :: ExceptT ServantError IO [UserSummary]
67−getUser :: Username -> ExceptT ServantError IO UserDetailed
68−getPackages :: ExceptT ServantError IO [Package]
69−getUsers :<|> getUser :<|> getPackages =
70− client hackageAPI (BaseUrl Http "hackage.haskell.org" 80 "") manager
71−
72−main :: IO ()
73−main = print =<< uselessNumbers
74−
75−uselessNumbers :: IO (Either ServantError ())
76−uselessNumbers = runExceptT $ do
77− users <- getUsers
78− liftIO . putStrLn $ show (length users) ++ " users"
79−
80− user <- liftIO $ do
81− putStrLn "Enter a valid hackage username"
82− T.getLine
83− userDetailed <- getUser user
84− liftIO . T.putStrLn $ user <> " maintains " <> T.pack (show (length $ groups userDetailed)) <> " packages"
85−
86− packages <- getPackages
87− let monadPackages = filter (isMonadPackage . packageName) packages
88− liftIO . putStrLn $ show (length monadPackages) ++ " monad packages"
89−
90− where isMonadPackage = T.isInfixOf "monad"
servant-examples/include/overlapping-compat.hdeleted−8
@@ -1,8 +0,0 @@
1−#if __GLASGOW_HASKELL__ >= 710
2−#define OVERLAPPABLE_ {-# OVERLAPPABLE #-}
3−#define OVERLAPPING_ {-# OVERLAPPING #-}
4−#else
5−{-# LANGUAGE OverlappingInstances #-}
6−#define OVERLAPPABLE_
7−#define OVERLAPPING_
8−#endif
servant-examples/servant-examples.cabaldeleted−148
@@ -1,148 +0,0 @@
1−name: servant-examples
2−version: 0.5
3−synopsis: Example programs for servant
4−description: Example programs for servant,
5− showcasing solutions to common needs.
6−homepage: http://haskell-servant.github.io/
7−license: BSD3
8−license-file: LICENSE
9−author: Servant Contributors
10−maintainer: haskell-servant-maintainers@googlegroups.com
11−copyright: 2015-2016 Servant Contributors
12−category: Web
13−build-type: Simple
14−cabal-version: >=1.10
15−bug-reports: http://github.com/haskell-servant/servant/issues
16−source-repository head
17− type: git
18− location: http://github.com/haskell-servant/servant.git
19−
20−executable tutorial
21− main-is: tutorial.hs
22− other-modules: T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
23− ghc-options: -Wall -fno-warn-unused-binds -fno-warn-name-shadowing -fno-warn-orphans -fno-warn-unused-imports
24− build-depends:
25− aeson >= 0.8
26− , base >= 4.7 && < 5
27− , bytestring
28− , directory
29− , http-types
30− , js-jquery
31− , lucid
32− , random
33− , servant == 0.5.*
34− , servant-docs == 0.5.*
35− , servant-js == 0.5.*
36− , servant-lucid == 0.5.*
37− , servant-server == 0.5.*
38− , text
39− , time
40− , transformers
41− , transformers-compat
42− , wai
43− , warp
44− hs-source-dirs: tutorial
45− default-language: Haskell2010
46−
47−executable t8-main
48− main-is: t8-main.hs
49− other-modules: T3, T8
50− hs-source-dirs: tutorial
51− default-language: Haskell2010
52− ghc-options: -Wall -fno-warn-unused-binds -fno-warn-name-shadowing
53− build-depends:
54− aeson
55− , base >= 4.7 && < 5
56− , http-client > 0.4 && < 0.5
57− , servant == 0.5.*
58− , servant-client == 0.5.*
59− , servant-server == 0.5.*
60− , transformers
61− , transformers-compat
62− , wai
63−
64−executable hackage
65− main-is: hackage.hs
66− build-depends:
67− aeson >= 0.8
68− , base >=4.7 && < 5
69− , http-client > 0.4 && < 0.5
70− , servant == 0.5.*
71− , servant-client == 0.5.*
72− , text
73− , transformers
74− , transformers-compat
75− hs-source-dirs: hackage
76− default-language: Haskell2010
77−
78−executable wai-middleware
79− main-is: wai-middleware.hs
80− build-depends:
81− aeson >= 0.8
82− , base >= 4.7 && < 5
83− , servant == 0.5.*
84− , servant-server == 0.5.*
85− , text
86− , wai
87− , wai-extra
88− , warp
89− hs-source-dirs: wai-middleware
90− default-language: Haskell2010
91−
92−executable basic-auth
93− main-is: basic-auth.hs
94− ghc-options: -Wall -fno-warn-unused-binds -fno-warn-name-shadowing
95− build-depends:
96− aeson >= 0.8
97− , base >= 4.7 && < 5
98− , bytestring
99− , http-types
100− , servant == 0.5.*
101− , servant-server == 0.5.*
102− , text
103− , wai
104− , warp
105− hs-source-dirs: basic-auth
106− default-language: Haskell2010
107−
108−executable auth-combinator
109− main-is: auth-combinator.hs
110− ghc-options: -Wall -fno-warn-unused-binds -fno-warn-name-shadowing
111− build-depends:
112− aeson >= 0.8
113− , base >= 4.7 && < 5
114− , bytestring
115− , containers
116− , http-types
117− , servant == 0.5.*
118− , servant-server == 0.5.*
119− , text
120− , transformers
121− , wai
122− , warp
123− hs-source-dirs: auth-combinator
124− default-language: Haskell2010
125−
126−executable socket-io-chat
127− main-is: socket-io-chat.hs
128− ghc-options: -Wall -fno-warn-unused-binds -fno-warn-name-shadowing
129− other-modules: Chat
130− build-depends:
131− aeson >= 0.8
132− , base >= 4.7 && < 5
133− , bytestring
134− , http-types
135− , servant == 0.5.*
136− , servant-server == 0.5.*
137− , socket-io
138− , engine-io
139− , engine-io-wai
140− , text
141− , wai
142− , warp
143− , transformers
144− , stm
145− , mtl
146− ghc-options: -Wall -O2 -threaded
147− hs-source-dirs: socket-io-chat
148− default-language: Haskell2010
servant-examples/socket-io-chat/Chat.hsdeleted−109
@@ -1,109 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE OverloadedStrings #-}
3−{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE KindSignatures #-}
5−{-# LANGUAGE RankNTypes #-}
6−
7−module Chat (eioServer, ServerState (..)) where
8−
9−import Prelude hiding (mapM_)
10−
11−#if !MIN_VERSION_base(4,8,0)
12−import Control.Applicative ((<$>), pure)
13−#endif
14−import Control.Monad.State.Class (MonadState)
15−import Control.Monad.IO.Class (MonadIO)
16−import Control.Monad.IO.Class (liftIO)
17−import Data.Aeson ((.=))
18−import Data.Foldable (mapM_)
19−
20−import qualified Control.Concurrent.STM as STM
21−import qualified Data.Aeson as Aeson
22−import qualified Data.Text as Text
23−import qualified Network.SocketIO as SocketIO
24−
25−
26−data AddUser = AddUser Text.Text
27−
28−instance Aeson.FromJSON AddUser where
29− parseJSON = Aeson.withText "AddUser" $ pure . AddUser
30−
31−
32−data NumConnected = NumConnected !Int
33−
34−instance Aeson.ToJSON NumConnected where
35− toJSON (NumConnected n) = Aeson.object [ "numUsers" .= n]
36−
37−
38−data NewMessage = NewMessage Text.Text
39−
40−instance Aeson.FromJSON NewMessage where
41− parseJSON = Aeson.withText "NewMessage" $ pure . NewMessage
42−
43−
44−data Said = Said Text.Text Text.Text
45−
46−instance Aeson.ToJSON Said where
47− toJSON (Said username message) = Aeson.object
48− [ "username" .= username
49− , "message" .= message
50− ]
51−
52−data UserName = UserName Text.Text
53−
54−instance Aeson.ToJSON UserName where
55− toJSON (UserName un) = Aeson.object [ "username" .= un ]
56−
57−
58−data UserJoined = UserJoined Text.Text Int
59−
60−instance Aeson.ToJSON UserJoined where
61− toJSON (UserJoined un n) = Aeson.object
62− [ "username" .= un
63− , "numUsers" .= n
64− ]
65−
66−
67−--------------------------------------------------------------------------------
68−data ServerState = ServerState { ssNConnected :: STM.TVar Int }
69−
70−--server :: ServerState -> StateT SocketIO.RoutingTable Snap.Snap ()
71−eioServer :: forall (m :: * -> *). (MonadState SocketIO.RoutingTable m, MonadIO m) => ServerState -> m ()
72−eioServer state = do
73− userNameMVar <- liftIO STM.newEmptyTMVarIO
74− let forUserName m = liftIO (STM.atomically (STM.tryReadTMVar userNameMVar)) >>= mapM_ m
75−
76− SocketIO.on "new message" $ \(NewMessage message) ->
77− forUserName $ \userName ->
78− SocketIO.broadcast "new message" (Said userName message)
79−
80− SocketIO.on "add user" $ \(AddUser userName) -> do
81− n <- liftIO $ STM.atomically $ do
82− n <- (+ 1) <$> STM.readTVar (ssNConnected state)
83− STM.putTMVar userNameMVar userName
84− STM.writeTVar (ssNConnected state) n
85− return n
86−
87− SocketIO.emit "login" (NumConnected n)
88− SocketIO.broadcast "user joined" (UserJoined userName n)
89−
90− SocketIO.appendDisconnectHandler $ do
91− (n, mUserName) <- liftIO $ STM.atomically $ do
92− n <- (+ (-1)) <$> STM.readTVar (ssNConnected state)
93− mUserName <- STM.tryReadTMVar userNameMVar
94− STM.writeTVar (ssNConnected state) n
95− return (n, mUserName)
96−
97− case mUserName of
98− Nothing -> return ()
99− Just userName ->
100− SocketIO.broadcast "user left" (UserJoined userName n)
101−
102− SocketIO.on "typing" $
103− forUserName $ \userName ->
104− SocketIO.broadcast "typing" (UserName userName)
105−
106− SocketIO.on "stop typing" $
107− forUserName $ \userName ->
108− SocketIO.broadcast "stop typing" (UserName userName)
109−
servant-examples/socket-io-chat/resources/index.htmldeleted−28
@@ -1,28 +0,0 @@
1−<!doctype html>
2−<html lang="en">
3−<head>
4− <meta charset="UTF-8">
5− <title>Socket.IO Chat Example</title>
6− <link rel="stylesheet" href="style.css">
7−</head>
8−<body>
9− <ul class="pages">
10− <li class="chat page">
11− <div class="chatArea">
12− <ul class="messages"></ul>
13− </div>
14− <input class="inputMessage" placeholder="Type here..."/>
15− </li>
16− <li class="login page">
17− <div class="form">
18− <h3 class="title">What's your nickname?</h3>
19− <input class="usernameInput" type="text" maxlength="14" />
20− </div>
21− </li>
22− </ul>
23−
24− <script src="https://cdn.socket.io/socket.io-1.2.1.js"></script>
25− <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
26− <script src="/main.js"></script>
27−</body>
28−</html>
servant-examples/socket-io-chat/resources/main.jsdeleted−274
@@ -1,274 +0,0 @@
1−$(function() {
2− var FADE_TIME = 150; // ms
3− var TYPING_TIMER_LENGTH = 400; // ms
4− var COLORS = [
5− '#e21400', '#91580f', '#f8a700', '#f78b00',
6− '#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
7− '#3b88eb', '#3824aa', '#a700ff', '#d300e7'
8− ];
9−
10− // Initialize varibles
11− var $window = $(window);
12− var $usernameInput = $('.usernameInput'); // Input for username
13− var $messages = $('.messages'); // Messages area
14− var $inputMessage = $('.inputMessage'); // Input message input box
15−
16− var $loginPage = $('.login.page'); // The login page
17− var $chatPage = $('.chat.page'); // The chatroom page
18−
19− // Prompt for setting a username
20− var username;
21− var connected = false;
22− var typing = false;
23− var lastTypingTime;
24− var $currentInput = $usernameInput.focus();
25−
26− var socket = io();
27−
28− function addParticipantsMessage (data) {
29− var message = '';
30− if (data.numUsers === 1) {
31− message += "there's 1 participant";
32− } else {
33− message += "there're " + data.numUsers + " participants";
34− }
35− log(message);
36− }
37−
38− // Sets the client's username
39− function setUsername () {
40− username = cleanInput($usernameInput.val().trim());
41−
42− // If the username is valid
43− if (username) {
44− $loginPage.fadeOut();
45− $chatPage.show();
46− $loginPage.off('click');
47− $currentInput = $inputMessage.focus();
48−
49− // Tell the server your username
50− socket.emit('add user', username);
51− }
52− }
53−
54− // Sends a chat message
55− function sendMessage () {
56− var message = $inputMessage.val();
57− // Prevent markup from being injected into the message
58− message = cleanInput(message);
59− // if there is a non-empty message and a socket connection
60− if (message && connected) {
61− $inputMessage.val('');
62− addChatMessage({
63− username: username,
64− message: message
65− });
66− // tell server to execute 'new message' and send along one parameter
67− socket.emit('new message', message);
68− }
69− }
70−
71− // Log a message
72− function log (message, options) {
73− var $el = $('<li>').addClass('log').text(message);
74− addMessageElement($el, options);
75− }
76−
77− // Adds the visual chat message to the message list
78− function addChatMessage (data, options) {
79− // Don't fade the message in if there is an 'X was typing'
80− var $typingMessages = getTypingMessages(data);
81− options = options || {};
82− if ($typingMessages.length !== 0) {
83− options.fade = false;
84− $typingMessages.remove();
85− }
86−
87− var $usernameDiv = $('<span class="username"/>')
88− .text(data.username)
89− .css('color', getUsernameColor(data.username));
90− var $messageBodyDiv = $('<span class="messageBody">')
91− .text(data.message);
92−
93− var typingClass = data.typing ? 'typing' : '';
94− var $messageDiv = $('<li class="message"/>')
95− .data('username', data.username)
96− .addClass(typingClass)
97− .append($usernameDiv, $messageBodyDiv);
98−
99− addMessageElement($messageDiv, options);
100− }
101−
102− // Adds the visual chat typing message
103− function addChatTyping (data) {
104− data.typing = true;
105− data.message = 'is typing';
106− addChatMessage(data);
107− }
108−
109− // Removes the visual chat typing message
110− function removeChatTyping (data) {
111− getTypingMessages(data).fadeOut(function () {
112− $(this).remove();
113− });
114− }
115−
116− // Adds a message element to the messages and scrolls to the bottom
117− // el - The element to add as a message
118− // options.fade - If the element should fade-in (default = true)
119− // options.prepend - If the element should prepend
120− // all other messages (default = false)
121− function addMessageElement (el, options) {
122− var $el = $(el);
123−
124− // Setup default options
125− if (!options) {
126− options = {};
127− }
128− if (typeof options.fade === 'undefined') {
129− options.fade = true;
130− }
131− if (typeof options.prepend === 'undefined') {
132− options.prepend = false;
133− }
134−
135− // Apply options
136− if (options.fade) {
137− $el.hide().fadeIn(FADE_TIME);
138− }
139− if (options.prepend) {
140− $messages.prepend($el);
141− } else {
142− $messages.append($el);
143− }
144− $messages[0].scrollTop = $messages[0].scrollHeight;
145− }
146−
147− // Prevents input from having injected markup
148− function cleanInput (input) {
149− return $('<div/>').text(input).text();
150− }
151−
152− // Updates the typing event
153− function updateTyping () {
154− if (connected) {
155− if (!typing) {
156− typing = true;
157− socket.emit('typing');
158− }
159− lastTypingTime = (new Date()).getTime();
160−
161− setTimeout(function () {
162− var typingTimer = (new Date()).getTime();
163− var timeDiff = typingTimer - lastTypingTime;
164− if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
165− socket.emit('stop typing');
166− typing = false;
167− }
168− }, TYPING_TIMER_LENGTH);
169− }
170− }
171−
172− // Gets the 'X is typing' messages of a user
173− function getTypingMessages (data) {
174− return $('.typing.message').filter(function (i) {
175− return $(this).data('username') === data.username;
176− });
177− }
178−
179− // Gets the color of a username through our hash function
180− function getUsernameColor (username) {
181− // Compute hash code
182− var hash = 7;
183− for (var i = 0; i < username.length; i++) {
184− hash = username.charCodeAt(i) + (hash << 5) - hash;
185− }
186− // Calculate color
187− var index = Math.abs(hash % COLORS.length);
188− return COLORS[index];
189− }
190−
191− // Keyboard events
192−
193− $window.keydown(function (event) {
194− // Auto-focus the current input when a key is typed
195− if (!(event.ctrlKey || event.metaKey || event.altKey)) {
196− $currentInput.focus();
197− }
198− // When the client hits ENTER on their keyboard
199− if (event.which === 13) {
200− if (username) {
201− sendMessage();
202− socket.emit('stop typing');
203− typing = false;
204− } else {
205− setUsername();
206− }
207− }
208− });
209−
210− $inputMessage.on('input', function() {
211− updateTyping();
212− });
213−
214− // Click events
215−
216− // Focus input when clicking anywhere on login page
217− $loginPage.click(function () {
218− $currentInput.focus();
219− });
220−
221− // Focus input when clicking on the message input's border
222− $inputMessage.click(function () {
223− $inputMessage.focus();
224− });
225−
226− // Socket events
227− socket.on('connected', function (data) {
228− console.log('connected:', data);
229− });
230−
231− // Socket events
232− socket.on('changes', function (data) {
233− console.log('changes:', data);
234− });
235−
236− // Whenever the server emits 'login', log the login message
237− socket.on('login', function (data) {
238− connected = true;
239− // Display the welcome message
240− var message = "Welcome to Socket.IO Chat &mdash; ";
241− log(message, {
242− prepend: true
243− });
244− addParticipantsMessage(data);
245− });
246−
247− // Whenever the server emits 'new message', update the chat body
248− socket.on('new message', function (data) {
249− addChatMessage(data);
250− });
251−
252− // Whenever the server emits 'user joined', log it in the chat body
253− socket.on('user joined', function (data) {
254− log(data.username + ' joined');
255− addParticipantsMessage(data);
256− });
257−
258− // Whenever the server emits 'user left', log it in the chat body
259− socket.on('user left', function (data) {
260− log(data.username + ' left');
261− addParticipantsMessage(data);
262− removeChatTyping(data);
263− });
264−
265− // Whenever the server emits 'typing', show the typing message
266− socket.on('typing', function (data) {
267− addChatTyping(data);
268− });
269−
270− // Whenever the server emits 'stop typing', kill the typing message
271− socket.on('stop typing', function (data) {
272− removeChatTyping(data);
273− });
274−});
servant-examples/socket-io-chat/resources/style.cssdeleted−150
@@ -1,150 +0,0 @@
1−/* Fix user-agent */
2−
3−* {
4− box-sizing: border-box;
5−}
6−
7−html {
8− font-weight: 300;
9− -webkit-font-smoothing: antialiased;
10−}
11−
12−html, input {
13− font-family:
14− "HelveticaNeue-Light",
15− "Helvetica Neue Light",
16− "Helvetica Neue",
17− Helvetica,
18− Arial,
19− "Lucida Grande",
20− sans-serif;
21−}
22−
23−html, body {
24− height: 100%;
25− margin: 0;
26− padding: 0;
27−}
28−
29−ul {
30− list-style: none;
31− word-wrap: break-word;
32−}
33−
34−/* Pages */
35−
36−.pages {
37− height: 100%;
38− margin: 0;
39− padding: 0;
40− width: 100%;
41−}
42−
43−.page {
44− height: 100%;
45− position: absolute;
46− width: 100%;
47−}
48−
49−/* Login Page */
50−
51−.login.page {
52− background-color: #000;
53−}
54−
55−.login.page .form {
56− height: 100px;
57− margin-top: -100px;
58− position: absolute;
59−
60− text-align: center;
61− top: 50%;
62− width: 100%;
63−}
64−
65−.login.page .form .usernameInput {
66− background-color: transparent;
67− border: none;
68− border-bottom: 2px solid #fff;
69− outline: none;
70− padding-bottom: 15px;
71− text-align: center;
72− width: 400px;
73−}
74−
75−.login.page .title {
76− font-size: 200%;
77−}
78−
79−.login.page .usernameInput {
80− font-size: 200%;
81− letter-spacing: 3px;
82−}
83−
84−.login.page .title, .login.page .usernameInput {
85− color: #fff;
86− font-weight: 100;
87−}
88−
89−/* Chat page */
90−
91−.chat.page {
92− display: none;
93−}
94−
95−/* Font */
96−
97−.messages {
98− font-size: 150%;
99−}
100−
101−.inputMessage {
102− font-size: 100%;
103−}
104−
105−.log {
106− color: gray;
107− font-size: 70%;
108− margin: 5px;
109− text-align: center;
110−}
111−
112−/* Messages */
113−
114−.chatArea {
115− height: 100%;
116− padding-bottom: 60px;
117−}
118−
119−.messages {
120− height: 100%;
121− margin: 0;
122− overflow-y: scroll;
123− padding: 10px 20px 10px 20px;
124−}
125−
126−.message.typing .messageBody {
127− color: gray;
128−}
129−
130−.username {
131− float: left;
132− font-weight: 700;
133− overflow: hidden;
134− padding-right: 15px;
135− text-align: right;
136−}
137−
138−/* Input */
139−
140−.inputMessage {
141− border: 10px solid #000;
142− bottom: 0;
143− height: 60px;
144− left: 0;
145− outline: none;
146− padding-left: 10px;
147− position: absolute;
148− right: 0;
149− width: 100%;
150−}
servant-examples/socket-io-chat/socket-io-chat.hsdeleted−54
@@ -1,54 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE TypeOperators #-}
5−
6−
7−import Data.Monoid ((<>))
8−#if !MIN_VERSION_base(4,8,0)
9−import Control.Applicative ((<$>))
10−#endif
11−import Network.EngineIO.Wai
12−import Network.Wai
13−import Network.Wai.Handler.Warp (run)
14−import Servant
15−
16−
17−import qualified Control.Concurrent.STM as STM
18−import qualified Network.SocketIO as SocketIO
19−
20−
21−import Chat (ServerState (..), eioServer)
22−
23−
24−type API = "socket.io" :> Raw
25− :<|> Raw
26−
27−
28−api :: Proxy API
29−api = Proxy
30−
31−
32−server :: WaiMonad () -> Server API
33−server sHandler = socketIOHandler
34− :<|> serveDirectory "socket-io-chat/resources"
35−
36− where
37− socketIOHandler req respond = toWaiApplication sHandler req respond
38−
39−
40−app :: WaiMonad () -> Application
41−app sHandler = serve api $ server sHandler
42−
43−port :: Int
44−port = 3001
45−
46−
47−main :: IO ()
48−main = do
49− state <- ServerState <$> STM.newTVarIO 0
50− sHandler <- SocketIO.initialize waiAPI (eioServer state)
51− putStrLn $ "Running on " <> show port
52− run port $ app sHandler
53−
54−
servant-examples/tinc.yamldeleted−15
@@ -1,15 +0,0 @@
1−dependencies:
2− - name: servant
3− path: ../servant
4− - name: servant-server
5− path: ../servant-server
6− - name: servant-client
7− path: ../servant-client
8− - name: servant-js
9− path: ../servant-js
10− - name: servant-lucid
11− path: ../servant-lucid
12− - name: servant-docs
13− path: ../servant-docs
14− - name: servant-foreign
15− path: ../servant-foreign
servant-examples/tutorial/T1.hsdeleted−45
@@ -1,45 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE DeriveGeneric #-}
4−{-# LANGUAGE TypeFamilies #-}
5−{-# LANGUAGE TypeOperators #-}
6−module T1 where
7−
8−import Data.Aeson
9−import Data.Time.Calendar
10−import GHC.Generics
11−import Network.Wai
12−import Servant
13−
14−data User = User
15− { name :: String
16− , age :: Int
17− , email :: String
18− , registration_date :: Day
19− } deriving (Eq, Show, Generic)
20−
21−#if !MIN_VERSION_aeson(0,10,0)
22−-- orphan ToJSON instance for Day. necessary to derive one for User
23−instance ToJSON Day where
24− -- display a day in YYYY-mm-dd format
25− toJSON d = toJSON (showGregorian d)
26−#endif
27−
28−instance ToJSON User
29−
30−type UserAPI = "users" :> Get '[JSON] [User]
31−
32−users :: [User]
33−users =
34− [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
35− , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
36− ]
37−
38−userAPI :: Proxy UserAPI
39−userAPI = Proxy
40−
41−server :: Server UserAPI
42−server = return users
43−
44−app :: Application
45−app = serve userAPI server
servant-examples/tutorial/T10.hsdeleted−71
@@ -1,71 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE FlexibleInstances #-}
3−{-# LANGUAGE MultiParamTypeClasses #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−{-# LANGUAGE TypeFamilies #-}
6−{-# LANGUAGE TypeOperators #-}
7−module T10 where
8−
9−import Data.ByteString.Lazy (ByteString)
10−import Data.Text.Lazy (pack)
11−import Data.Text.Lazy.Encoding (encodeUtf8)
12−import Network.HTTP.Types
13−import Network.Wai
14−import Servant
15−import Servant.Docs
16−import qualified T3
17−
18−type DocsAPI = T3.API :<|> Raw
19−
20−instance ToCapture (Capture "x" Int) where
21− toCapture _ = DocCapture "x" "(integer) position on the x axis"
22−
23−instance ToCapture (Capture "y" Int) where
24− toCapture _ = DocCapture "y" "(integer) position on the y axis"
25−
26−instance ToSample T3.Position where
27− toSamples _ = singleSample (T3.Position 3 14)
28−
29−instance ToParam (QueryParam "name" String) where
30− toParam _ =
31− DocQueryParam "name"
32− ["Alp", "John Doe", "..."]
33− "Name of the person to say hello to."
34− Normal
35−
36−instance ToSample T3.HelloMessage where
37− toSamples _ =
38− [ ("When a value is provided for 'name'", T3.HelloMessage "Hello, Alp")
39− , ("When 'name' is not specified", T3.HelloMessage "Hello, anonymous coward")
40− ]
41−
42−ci :: T3.ClientInfo
43−ci = T3.ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
44−
45−instance ToSample T3.ClientInfo where
46− toSamples _ = singleSample ci
47−
48−instance ToSample T3.Email where
49− toSamples _ = singleSample (T3.emailForClient ci)
50−
51−api :: Proxy DocsAPI
52−api = Proxy
53−
54−docsBS :: ByteString
55−docsBS = encodeUtf8
56− . pack
57− . markdown
58− $ docsWithIntros [intro] T3.api
59−
60− where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]
61−
62−server :: Server DocsAPI
63−server = T3.server :<|> serveDocs
64−
65− where serveDocs _ respond =
66− respond $ responseLBS ok200 [plain] docsBS
67−
68− plain = ("Content-Type", "text/plain")
69−
70−app :: Application
71−app = serve api server
servant-examples/tutorial/T2.hsdeleted−52
@@ -1,52 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE DeriveGeneric #-}
4−{-# LANGUAGE TypeFamilies #-}
5−{-# LANGUAGE TypeOperators #-}
6−module T2 where
7−
8−import Data.Aeson
9−import Data.Time.Calendar
10−import GHC.Generics
11−import Network.Wai
12−import Servant
13−
14−data User = User
15− { name :: String
16− , age :: Int
17− , email :: String
18− , registration_date :: Day
19− } deriving (Eq, Show, Generic)
20−
21−#if !MIN_VERSION_aeson(0,10,0)
22−-- orphan ToJSON instance for Day. necessary to derive one for User
23−instance ToJSON Day where
24− -- display a day in YYYY-mm-dd format
25− toJSON d = toJSON (showGregorian d)
26−#endif
27−
28−instance ToJSON User
29−
30−type UserAPI = "users" :> Get '[JSON] [User]
31− :<|> "albert" :> Get '[JSON] User
32− :<|> "isaac" :> Get '[JSON] User
33−
34−isaac :: User
35−isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
36−
37−albert :: User
38−albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
39−
40−users :: [User]
41−users = [isaac, albert]
42−
43−userAPI :: Proxy UserAPI
44−userAPI = Proxy
45−
46−server :: Server UserAPI
47−server = return users
48− :<|> return albert
49− :<|> return isaac
50−
51−app :: Application
52−app = serve userAPI server
servant-examples/tutorial/T3.hsdeleted−84
@@ -1,84 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE TypeFamilies #-}
4−{-# LANGUAGE TypeOperators #-}
5−module T3 where
6−
7−import Control.Monad.Trans.Except
8−import Data.Aeson
9−import Data.List
10−import GHC.Generics
11−import Network.Wai
12−import Servant
13−
14−data Position = Position
15− { x :: Int
16− , y :: Int
17− } deriving (Show, Generic)
18−
19−instance FromJSON Position
20−instance ToJSON Position
21−
22−newtype HelloMessage = HelloMessage { msg :: String }
23− deriving (Show, Generic)
24−
25−instance FromJSON HelloMessage
26−instance ToJSON HelloMessage
27−
28−data ClientInfo = ClientInfo
29− { name :: String
30− , email :: String
31− , age :: Int
32− , interested_in :: [String]
33− } deriving (Show, Generic)
34−
35−instance FromJSON ClientInfo
36−instance ToJSON ClientInfo
37−
38−data Email = Email
39− { from :: String
40− , to :: String
41− , subject :: String
42− , body :: String
43− } deriving (Show, Generic)
44−
45−instance FromJSON Email
46−instance ToJSON Email
47−
48−emailForClient :: ClientInfo -> Email
49−emailForClient c = Email from' to' subject' body'
50−
51− where from' = "great@company.com"
52− to' = email c
53− subject' = "Hey " ++ name c ++ ", we miss you!"
54− body' = "Hi " ++ name c ++ ",\n\n"
55− ++ "Since you've recently turned " ++ show (age c)
56− ++ ", have you checked out our latest "
57− ++ intercalate ", " (interested_in c)
58− ++ " products? Give us a visit!"
59−
60−type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
61− :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
62− :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
63−
64−api :: Proxy API
65−api = Proxy
66−
67−server :: Server API
68−server = position
69− :<|> hello
70− :<|> marketing
71−
72− where position :: Int -> Int -> ExceptT ServantErr IO Position
73− position x y = return (Position x y)
74−
75− hello :: Maybe String -> ExceptT ServantErr IO HelloMessage
76− hello mname = return . HelloMessage $ case mname of
77− Nothing -> "Hello, anonymous coward"
78− Just n -> "Hello, " ++ n
79−
80− marketing :: ClientInfo -> ExceptT ServantErr IO Email
81− marketing clientinfo = return (emailForClient clientinfo)
82−
83−app :: Application
84−app = serve api server
servant-examples/tutorial/T4.hsdeleted−63
@@ -1,63 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−{-# LANGUAGE TypeFamilies #-}
6−{-# LANGUAGE TypeOperators #-}
7−module T4 where
8−
9−import Data.Aeson
10−import Data.Foldable (foldMap)
11−import GHC.Generics
12−import Lucid
13−import Network.Wai
14−import Servant
15−import Servant.HTML.Lucid
16−
17−data Person = Person
18− { firstName :: String
19− , lastName :: String
20− , age :: Int
21− } deriving Generic -- for the JSON instance
22−
23−-- JSON serialization
24−instance ToJSON Person
25−
26−-- HTML serialization of a single person
27−instance ToHtml Person where
28− toHtml person =
29− tr_ $ do
30− td_ (toHtml $ firstName person)
31− td_ (toHtml $ lastName person)
32− td_ (toHtml . show $ age person)
33−
34− toHtmlRaw = toHtml
35−
36−-- HTML serialization of a list of persons
37−instance ToHtml [Person] where
38− toHtml persons = table_ $ do
39− tr_ $ do
40− th_ "first name"
41− th_ "last name"
42− th_ "age"
43−
44− foldMap toHtml persons
45−
46− toHtmlRaw = toHtml
47−
48−persons :: [Person]
49−persons =
50− [ Person "Isaac" "Newton" 372
51− , Person "Albert" "Einstein" 136
52− ]
53−
54−type PersonAPI = "persons" :> Get '[JSON, HTML] [Person]
55−
56−personAPI :: Proxy PersonAPI
57−personAPI = Proxy
58−
59−server :: Server PersonAPI
60−server = return persons
61−
62−app :: Application
63−app = serve personAPI server
servant-examples/tutorial/T5.hsdeleted−37
@@ -1,37 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE TypeFamilies #-}
5−{-# LANGUAGE TypeOperators #-}
6−module T5 where
7−
8−import Control.Monad.IO.Class
9−import Control.Monad.Trans.Except
10−import Data.Aeson
11−import GHC.Generics
12−import Network.Wai
13−import Servant
14−import System.Directory
15−
16−type IOAPI = "myfile.txt" :> Get '[JSON] FileContent
17−
18−ioAPI :: Proxy IOAPI
19−ioAPI = Proxy
20−
21−newtype FileContent = FileContent
22− { content :: String }
23− deriving Generic
24−
25−instance ToJSON FileContent
26−
27−server :: Server IOAPI
28−server = do
29− exists <- liftIO (doesFileExist "myfile.txt")
30− if exists
31− then liftIO (readFile "myfile.txt") >>= return . FileContent
32− else throwE custom404Err
33−
34− where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
35−
36−app :: Application
37−app = serve ioAPI server
servant-examples/tutorial/T6.hsdeleted−18
@@ -1,18 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE TypeFamilies #-}
3−{-# LANGUAGE TypeOperators #-}
4−module T6 where
5−
6−import Network.Wai
7−import Servant
8−
9−type API = "code" :> Raw
10−
11−api :: Proxy API
12−api = Proxy
13−
14−server :: Server API
15−server = serveDirectory "tutorial"
16−
17−app :: Application
18−app = serve api server
servant-examples/tutorial/T7.hsdeleted−33
@@ -1,33 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE TypeFamilies #-}
3−{-# LANGUAGE TypeOperators #-}
4−module T7 where
5−
6−import Control.Monad.Trans.Except
7−import Control.Monad.Trans.Reader
8−import Network.Wai
9−import Servant
10−
11−type ReaderAPI = "a" :> Get '[JSON] Int
12− :<|> "b" :> Get '[JSON] String
13−
14−readerAPI :: Proxy ReaderAPI
15−readerAPI = Proxy
16−
17−readerServerT :: ServerT ReaderAPI (Reader String)
18−readerServerT = a :<|> b
19−
20− where a :: Reader String Int
21− a = return 1797
22−
23− b :: Reader String String
24− b = ask
25−
26−readerServer :: Server ReaderAPI
27−readerServer = enter readerToEither readerServerT
28−
29− where readerToEither :: Reader String :~> ExceptT ServantErr IO
30− readerToEither = Nat $ \r -> return (runReader r "hi")
31−
32−app :: Application
33−app = serve readerAPI readerServer
servant-examples/tutorial/T8.hsdeleted−49
@@ -1,49 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE TypeFamilies #-}
3−{-# LANGUAGE TypeOperators #-}
4−module T8 where
5−
6−import Control.Monad.Trans.Except
7−import Network.HTTP.Client (Manager, defaultManagerSettings,
8− newManager)
9−import Servant
10−import Servant.Client
11−import System.IO.Unsafe (unsafePerformIO)
12−
13−import T3
14−
15−position :: Int -- ^ value for "x"
16− -> Int -- ^ value for "y"
17− -> ExceptT ServantError IO Position
18−
19−hello :: Maybe String -- ^ an optional value for "name"
20− -> ExceptT ServantError IO HelloMessage
21−
22−marketing :: ClientInfo -- ^ value for the request body
23− -> ExceptT ServantError IO Email
24−
25−position :<|> hello :<|> marketing = client api baseUrl manager
26−
27−baseUrl :: BaseUrl
28−baseUrl = BaseUrl Http "localhost" 8081 ""
29−
30−{-# NOINLINE manager #-}
31−manager :: Manager
32−manager = unsafePerformIO $ newManager defaultManagerSettings
33−
34−queries :: ExceptT ServantError IO (Position, HelloMessage, Email)
35−queries = do
36− pos <- position 10 10
37− msg <- hello (Just "servant")
38− em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
39− return (pos, msg, em)
40−
41−run :: IO ()
42−run = do
43− res <- runExceptT queries
44− case res of
45− Left err -> putStrLn $ "Error: " ++ show err
46− Right (pos, msg, em) -> do
47− print pos
48− print msg
49− print em
servant-examples/tutorial/T9.hsdeleted−105
@@ -1,105 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE TypeFamilies #-}
5−{-# LANGUAGE TypeOperators #-}
6−module T9 where
7−
8−import Control.Applicative
9−import Control.Monad.IO.Class
10−import Data.Aeson
11−import Data.Text (Text)
12−import GHC.Generics
13−import Network.Wai
14−import Servant
15−import Servant.JS
16−import System.Random
17−
18−import qualified Data.Text as T
19−import qualified Data.Text.IO as TIO
20−import qualified Language.Javascript.JQuery as JQ
21−
22−data Point = Point
23− { x :: Double
24− , y :: Double
25− } deriving Generic
26−
27−instance ToJSON Point
28−
29−randomPoint :: MonadIO m => m Point
30−randomPoint = liftIO . getStdRandom $ \g ->
31− let (rx, g') = randomR (-1, 1) g
32− (ry, g'') = randomR (-1, 1) g'
33− in (Point rx ry, g'')
34−
35−data Search a = Search
36− { query :: Text
37− , results :: [a]
38− } deriving Generic
39−
40−mkSearch :: Text -> [a] -> Search a
41−mkSearch = Search
42−
43−instance ToJSON a => ToJSON (Search a)
44−
45−data Book = Book
46− { author :: Text
47− , title :: Text
48− , year :: Int
49− } deriving Generic
50−
51−instance ToJSON Book
52−
53−book :: Text -> Text -> Int -> Book
54−book = Book
55−
56−books :: [Book]
57−books =
58− [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
59− , book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008
60− , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
61− , book "Graham Hutton" "Programming in Haskell" 2007
62− , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
63− , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
64− ]
65−
66−searchBook :: Monad m => Maybe Text -> m (Search Book)
67−searchBook Nothing = return (mkSearch "" books)
68−searchBook (Just q) = return (mkSearch q books')
69−
70− where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
71− || q' `T.isInfixOf` T.toLower (title b)
72− )
73− books
74− q' = T.toLower q
75−
76−type API = "point" :> Get '[JSON] Point
77− :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
78−
79−type API' = API :<|> Raw
80−
81−api :: Proxy API
82−api = Proxy
83−
84−api' :: Proxy API'
85−api' = Proxy
86−
87−server :: Server API
88−server = randomPoint
89− :<|> searchBook
90−
91−server' :: Server API'
92−server' = server
93− :<|> serveDirectory "tutorial/t9"
94−
95−apiJS :: Text
96−apiJS = jsForAPI api jquery
97−
98−writeJSFiles :: IO ()
99−writeJSFiles = do
100− TIO.writeFile "tutorial/t9/api.js" apiJS
101− jq <- TIO.readFile =<< JQ.file
102− TIO.writeFile "tutorial/t9/jq.js" jq
103−
104−app :: Application
105−app = serve api' server'
servant-examples/tutorial/t8-main.hsdeleted−4
@@ -1,4 +0,0 @@
1−import T8
2−
3−main :: IO ()
4−main = run
servant-examples/tutorial/tutorial.hsdeleted−39
@@ -1,39 +0,0 @@
1−import Network.Wai
2−import Network.Wai.Handler.Warp
3−import System.Environment
4−
5−import qualified T1
6−import qualified T10
7−import qualified T2
8−import qualified T3
9−import qualified T4
10−import qualified T5
11−import qualified T6
12−import qualified T7
13−import qualified T9
14−
15−app :: String -> (Application -> IO ()) -> IO ()
16−app n f = case n of
17− "1" -> f T1.app
18− "2" -> f T2.app
19− "3" -> f T3.app
20− "4" -> f T4.app
21− "5" -> f T5.app
22− "6" -> f T6.app
23− "7" -> f T7.app
24− "8" -> f T3.app
25− "9" -> T9.writeJSFiles >> f T9.app
26− "10" -> f T10.app
27− _ -> usage
28−
29−main :: IO ()
30−main = do
31− args <- getArgs
32− case args of
33− [n] -> app n (run 8081)
34− _ -> usage
35−
36−usage :: IO ()
37−usage = do
38− putStrLn "Usage:\t tutorial N"
39− putStrLn "\t\twhere N is the number of the example you want to run."
servant-examples/wai-middleware/wai-middleware.hsdeleted−51
@@ -1,51 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−import Data.Aeson
5−import Data.Text
6−import GHC.Generics
7−import Network.Wai
8−import Network.Wai.Handler.Warp
9−import Network.Wai.Middleware.RequestLogger
10−import Servant
11−
12−data Product = Product
13− { name :: Text
14− , brand :: Text
15− , current_price_eur :: Double
16− , available :: Bool
17− } deriving (Eq, Show, Generic)
18−
19−instance ToJSON Product
20−
21−products :: [Product]
22−products = [p1, p2]
23−
24− where p1 = Product "Haskell laptop sticker"
25− "GHC Industries"
26− 2.50
27− True
28−
29− p2 = Product "Foldable USB drive"
30− "Well-Typed"
31− 13.99
32− False
33−
34−type SimpleAPI = Get '[JSON] [Product]
35−
36−simpleAPI :: Proxy SimpleAPI
37−simpleAPI = Proxy
38−
39−server :: Server SimpleAPI
40−server = return products
41−
42−-- logStdout :: Middleware
43−-- i.e, logStdout :: Application -> Application
44−-- serve :: Proxy api -> Context context -> Server api -> Application
45−-- so applying a middleware is really as simple as
46−-- applying a function to the result of 'serve'
47−app :: Application
48−app = logStdout (serve simpleAPI server)
49−
50−main :: IO ()
51−main = run 8080 app
sources.txt+1−1
@@ -5,7 +5,7 @@ servant-docs
55servant-foreign
66servant-js
77servant-server
8−servant-examples
98servant-blaze
109servant-lucid
1110servant-mock
11+doc/tutorial
stack.yaml+5−2
@@ -7,15 +7,18 @@ packages:
77- servant-cassava/
88- servant-client/
99- servant-docs/
10−- servant-examples/
1110- servant-foreign/
1211- servant-js/
1312- servant-lucid/
1413- servant-mock/
1514- servant-server/
15+- doc/tutorial
1616extra-deps:
1717- base-compat-0.9.0
1818- engine-io-wai-1.0.2
1919- control-monad-omega-0.3.1
2020- should-not-typecheck-2.0.1
21−resolver: nightly-2015-10-08
21+- markdown-unlit-0.4.0
22+- aeson-0.11.0.0
23+- fail-4.9.0.0
24+resolver: nightly-2016-03-17