on master help sign in
sundry tutorial improvements
e1312c1Julian K. Arni
doc/conf.py+5
@@ -94,6 +94,11 @@ exclude_patterns = ['_build', 'venv']
94# The name of the Pygments (syntax highlighting) style to use.
94# The name of the Pygments (syntax highlighting) style to use.
95pygments_style = 'sphinx'
95pygments_style = 'sphinx'
96
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+
97# A list of ignored prefixes for module index sorting.
102# A list of ignored prefixes for module index sorting.
98#modindex_common_prefix = []
103#modindex_common_prefix = []
99
104
doc/requirements.txt+1−1
@@ -10,7 +10,7 @@ Jinja2==2.8
10livereload==2.4.1
10livereload==2.4.1
11MarkupSafe==0.23
11MarkupSafe==0.23
12pathtools==0.1.2
12pathtools==0.1.2
13−Pygments==2.1
13+Pygments==2.1.1
14pytz==2015.7
14pytz==2015.7
15PyYAML==3.11
15PyYAML==3.11
16recommonmark==0.4.0
16recommonmark==0.4.0
doc/tutorial/Server.lhs+65−92
@@ -23,8 +23,7 @@ Usage: tutorial N
23 where N is the number of the example you want to run.
23 where N is the number of the example you want to run.
24```
24```
25
25
26−A first example
26+## A first example
27−===============
28
27
29Equipped with some basic knowledge about the way we represent API, let's now write our first webservice.
28Equipped with some basic knowledge about the way we represent API, let's now write our first webservice.
30
29
@@ -53,7 +52,6 @@ import Data.Aeson
53import Data.Aeson.Types
52import Data.Aeson.Types
54import Data.Attoparsec.ByteString
53import Data.Attoparsec.ByteString
55import Data.ByteString (ByteString)
54import Data.ByteString (ByteString)
56−import Data.Int
57import Data.List
55import Data.List
58import Data.String.Conversions
56import Data.String.Conversions
59import Data.Time.Calendar
57import Data.Time.Calendar
@@ -97,7 +95,7 @@ data User = User
97 { name :: String
95 { name :: String
98 , age :: Int
96 , age :: Int
99 , email :: String
97 , email :: String
100− , registration_date :: Day
98+ , registrationDate :: Day
101 } deriving (Eq, Show, Generic)
99 } deriving (Eq, Show, Generic)
102
100
103instance ToJSON User
101instance ToJSON User
@@ -179,8 +177,7 @@ $ curl http://localhost:8081/users
179[{"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"}]
177[{"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"}]
180```
178```
181
179
182−More endpoints
180+## More endpoints
183−==============
184
181
185What if we want more than one endpoint? Let's add `/albert` and `/isaac` to view the corresponding users encoded in JSON.
182What if we want more than one endpoint? Let's add `/albert` and `/isaac` to view the corresponding users encoded in JSON.
186
183
@@ -218,8 +215,7 @@ And that's it! You can run this example with
218`dist/build/tutorial/tutorial 2` and check out the data available
215`dist/build/tutorial/tutorial 2` and check out the data available
219at `/users`, `/albert` and `/isaac`.
216at `/users`, `/albert` and `/isaac`.
220
217
221−From combinators to handler arguments
218+## From combinators to handler arguments
222−=====================================
223
219
224Fine, we can write trivial webservices easily, but none of the two above use
220Fine, we can write trivial webservices easily, but none of the two above use
225any "fancy" combinator from servant. Let's address this and use `QueryParam`,
221any "fancy" combinator from servant. Let's address this and use `QueryParam`,
@@ -237,8 +233,8 @@ type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Posit
237 :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
233 :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
238
234
239data Position = Position
235data Position = Position
240− { x :: Int
236+ { xCoord :: Int
241− , y :: Int
237+ , yCoord :: Int
242 } deriving Generic
238 } deriving Generic
243
239
244instance ToJSON Position
240instance ToJSON Position
@@ -334,8 +330,7 @@ that get turned into arguments to the handlers, the type of the argument.
334 > - `QueryParams "something" a` and `MatrixParams "something" a` get turned into arguments of type `[a]`.
330 > - `QueryParams "something" a` and `MatrixParams "something" a` get turned into arguments of type `[a]`.
335 > - `ReqBody contentTypes a` gets turned into an argument of type `a`.
331 > - `ReqBody contentTypes a` gets turned into an argument of type `a`.
336
332
337−The `FromText`/`ToText` classes
333+## The `FromHttpApiData`/`ToHttpApiData` classes
338−===============================
339
334
340Wait... How does *servant* know how to decode the `Int`s from the URL? Or how
335Wait... How does *servant* know how to decode the `Int`s from the URL? Or how
341to decode a `ClientInfo` value from the request body? This is what this and the
336to decode a `ClientInfo` value from the request body? This is what this and the
@@ -343,54 +338,42 @@ following two sections address.
343
338
344`Capture`s and `QueryParam`s are represented by some textual value in URLs.
339`Capture`s and `QueryParam`s are represented by some textual value in URLs.
345`Header`s are similarly represented by a pair of a header name and a
340`Header`s are similarly represented by a pair of a header name and a
346−corresponding (textual) value in the request's "metadata". This is why we
341+corresponding (textual) value in the request's "metadata". How types are
347−decided to provide a pair of typeclasses, `FromText` and `ToText` which just
342+decoded from headers, captures, and query params is expressed in a class
348−let you say that you can respectively *extract* or *encode* values of some type
343+`FromHttpApiData` (from the package
349−*from*/*to* text. Here are the definitions:
344+[*http-api-data*](http://hackage.haskell.org/package/http-api-data)):
350
345
351``` haskell ignore
346``` haskell ignore
352−class FromText a where
347+class FromHttpApiData a where
353− fromText :: Text -> Maybe a
348+ {-# MINIMAL parseUrlPiece | parseQueryParam #-}
354−
349+ -- | Parse URL path piece.
355−class ToText a where
350+ parseUrlPiece :: Text -> Either Text a
356− toText :: a -> Text
351+ parseUrlPiece = parseQueryParam
357−```
358
352
359−And as long as the type that a `Capture`/`QueryParam`/`Header`/etc will be
353+ -- | Parse HTTP header value.
360−decoded to provides a `FromText` instance, it will Just Work. *servant*
354+ parseHeader :: ByteString -> Either Text a
361−provides a decent number of instances, but here are some examples of defining
355+ parseHeader = parseUrlPiece . decodeUtf8
362−your own.
363
356
364−``` haskell
357+ -- | Parse query param value.
365−-- A typical enumeration
358+ parseQueryParam :: Text -> Either Text a
366−data Direction
359+ parseQueryParam = parseUrlPiece
367− = Up
368− | Down
369− | Left
370− | Right
371−
372−newtype UserId = UserId Int64
373```
360```
374
361
375−or writing the instances by hand:
362+As you can see, as long as you provide either `parseUrlPiece` (for `Capture`s)
376−
363+or `parseQueryParam` (for `QueryParam`s), the other methods will be defined in
377−``` haskell ignore
364+terms of this.
378−instance FromText UserId where
379− fromText = fmap UserId fromText
380
365
381−instance ToText UserId where
366+*http-api-data* provides a decent number of instances, helpers for defining new
382− toText (UserId i) = toText i
367+ones, and wonderful documentation.
383−```
384
368
385There's not much else to say about these classes. You will need instances for
369There's not much else to say about these classes. You will need instances for
386−them when using `Capture`, `QueryParam`, `QueryParams`, `MatrixParam`,
370+them when using `Capture`, `QueryParam`, `QueryParams`, and `Header` with your
387−`MatrixParams` and `Header` with your types. You will need `FromText` instances
371+types. You will need `FromHttpApiData` instances for server-side request
388−for server-side request handlers and `ToText` instances only when using
372+handlers and `ToHttpApiData` instances only when using
389*servant-client*, as described in the [section about deriving haskell
373*servant-client*, as described in the [section about deriving haskell
390functions to query an API](/tutorial/client.html).
374functions to query an API](/tutorial/client.html).
391
375
392−Using content-types with your data types
376+## Using content-types with your data types
393−========================================
394
377
395The same principle was operating when decoding request bodies from JSON, and
378The same principle was operating when decoding request bodies from JSON, and
396responses *into* JSON. (JSON is just the running example - you can do this with
379responses *into* JSON. (JSON is just the running example - you can do this with
@@ -399,8 +382,8 @@ any content-type.)
399This section introduces a couple of typeclasses provided by *servant* that make
382This section introduces a couple of typeclasses provided by *servant* that make
400all of this work.
383all of this work.
401
384
402−The truth behind `JSON`
385+### The truth behind `JSON`
403−-----------------------
386+
404
387
405What exactly is `JSON`? Like the 3 other content types provided out of the box
388What exactly is `JSON`? Like the 3 other content types provided out of the box
406by *servant*, it's a really dumb data type.
389by *servant*, it's a really dumb data type.
@@ -464,8 +447,6 @@ And now the `MimeUnrender` class, which lets us extract values from lazy
464``` haskell ignore
447``` haskell ignore
465class Accept ctype => MimeUnrender ctype a where
448class Accept ctype => MimeUnrender ctype a where
466 mimeUnrender :: Proxy ctype -> ByteString -> Either String a
449 mimeUnrender :: Proxy ctype -> ByteString -> Either String a
467− -- alternatively:
468− mimeUnrender :: Proxy ctype -> (ByteString -> Either String a)
469```
450```
470
451
471We don't have much work to do there either, `Data.Aeson.eitherDecode` is
452We don't have much work to do there either, `Data.Aeson.eitherDecode` is
@@ -496,8 +477,7 @@ HTML representation of the data they want, ready to be included in any HTML
496document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/), simply by adding `Accept:
477document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/), simply by adding `Accept:
497text/html` to their request headers.
478text/html` to their request headers.
498
479
499−Case-studies: *servant-blaze* and *servant-lucid*
480+### Case-studies: *servant-blaze* and *servant-lucid*
500−-------------------------------------------------
501
481
502These days, most of the haskellers who write their HTML UIs directly from
482These days, most of the haskellers who write their HTML UIs directly from
503Haskell use either [blaze-html](http://hackage.haskell.org/package/blaze-html)
483Haskell use either [blaze-html](http://hackage.haskell.org/package/blaze-html)
@@ -615,8 +595,8 @@ instance ToHtml [Person] where
615We create some `Person` values and serve them as a list:
595We create some `Person` values and serve them as a list:
616
596
617``` haskell
597``` haskell
618−persons :: [Person]
598+people :: [Person]
619−persons =
599+people =
620 [ Person "Isaac" "Newton"
600 [ Person "Isaac" "Newton"
621 , Person "Albert" "Einstein"
601 , Person "Albert" "Einstein"
622 ]
602 ]
@@ -625,7 +605,7 @@ personAPI :: Proxy PersonAPI
625personAPI = Proxy
605personAPI = Proxy
626
606
627server4 :: Server PersonAPI
607server4 :: Server PersonAPI
628−server4 = return persons
608+server4 = return people
629
609
630app2 :: Application
610app2 :: Application
631app2 = serve personAPI EmptyConfig server4
611app2 = serve personAPI EmptyConfig server4
@@ -641,8 +621,7 @@ And we're good to go. You can run this example with `dist/build/tutorial/tutoria
641 # or just point your browser to http://localhost:8081/persons
621 # or just point your browser to http://localhost:8081/persons
642```
622```
643
623
644−The `ExceptT ServantErr IO` monad
624+## The `ExceptT ServantErr IO` monad
645−=================================
646
625
647At the heart of the handlers is the monad they run in, namely `ExceptT
626At the heart of the handlers is the monad they run in, namely `ExceptT
648ServantErr IO`. One might wonder: why this monad? The answer is that it is the
627ServantErr IO`. One might wonder: why this monad? The answer is that it is the
@@ -660,40 +639,39 @@ Let's recall some definitions.
660-- from the Prelude
639-- from the Prelude
661data Either e a = Left e | Right a
640data Either e a = Left e | Right a
662
641
663−-- from the 'either' package at
642+-- from the 'mtl' package at
664−-- http://hackage.haskell.org/package/either-4.3.3.2/docs/Control-Monad-Trans-Either.html
665−newtype ExceptT e m a
643+newtype ExceptT e m a = ExceptT ( m (Either e a) )
666− = ExceptT { runEitherT :: m (Either e a) }
667```
644```
668
645
669In short, this means that a handler of type `ExceptT ServantErr IO a` is simply
646In short, this means that a handler of type `ExceptT ServantErr IO a` is simply
670equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
647equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
671action that either returns an error or a result.
648action that either returns an error or a result.
672
649
673−The aforementioned `either` package is worth taking a look at. Perhaps most
650+The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)
674−importantly:
651+from which `ExceptT` comes is worth looking at.
675−
652+Perhaps most importantly, `ExceptT` is an instance of `MonadError`, so
676−``` haskell ignore
677−left :: Monad m => e -> ExceptT e m a
678−```
679−Allows you to return an error from your handler (whereas `return` is enough to
653+`throwError` can be used to return an error from your handler (whereas `return`
680−return a success).
654+ is enough to return a success).
681
655
682Most of what you'll be doing in your handlers is running some IO and,
656Most of what you'll be doing in your handlers is running some IO and,
683depending on the result, you might sometimes want to throw an error of some
657depending on the result, you might sometimes want to throw an error of some
684kind and abort early. The next two sections cover how to do just that.
658kind and abort early. The next two sections cover how to do just that.
685
659
686−Performing IO
660+### Performing IO
687−-------------
688
661
662+Another important instance from the list above is `MonadIO m => MonadIO
663+(ExceptT e m)`.
689−Another important instance from the list above is `MonadIO m => MonadIO (ExceptT e m)`. [`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html) is a class from the *transformers* package defined as:
664+[`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html)
665+is a class from the *transformers* package defined as:
690
666
691``` haskell ignore
667``` haskell ignore
692class Monad m => MonadIO m where
668class Monad m => MonadIO m where
693 liftIO :: IO a -> m a
669 liftIO :: IO a -> m a
694```
670```
695
671
696−Obviously, the `IO` monad provides a `MonadIO` instance. Hence for any type `e`, `ExceptT e IO` has a `MonadIO` instance. So if you want to run any kind of IO computation in your handlers, just use `liftIO`:
672+Obviously, the `IO` monad provides a `MonadIO` instance. Hence for any type
673+`e`, `ExceptT e IO` has a `MonadIO` instance. So if you want to run any kind of
674+IO computation in your handlers, just use `liftIO`:
697
675
698``` haskell
676``` haskell
699type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
677type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
@@ -710,8 +688,7 @@ server5 = do
710 return (FileContent filecontent)
688 return (FileContent filecontent)
711```
689```
712
690
713−Failing, through `ServantErr`
691+### Failing, through `ServantErr`
714−-----------------------------
715
692
716If you want to explicitly fail at providing the result promised by an endpoint
693If you want to explicitly fail at providing the result promised by an endpoint
717using the appropriate HTTP status code (not found, unauthorized, etc) and some
694using the appropriate HTTP status code (not found, unauthorized, etc) and some
@@ -787,8 +764,7 @@ query it, first without the file and then with the file.
787 {"content":"Hello\n"}
764 {"content":"Hello\n"}
788```
765```
789
766
790−Response headers
767+## Response headers
791−================
792
768
793To add headers to your response, use [addHeader](http://hackage.haskell.org/package/servant-0.4.4/docs/Servant-API-ResponseHeaders.html).
769To add headers to your response, use [addHeader](http://hackage.haskell.org/package/servant-0.4.4/docs/Servant-API-ResponseHeaders.html).
794Note that this changes the type of your API, as we can see in the following example:
770Note that this changes the type of your API, as we can see in the following example:
@@ -800,9 +776,9 @@ myHandler :: Server MyHandler
800myHandler = return $ addHeader 1797 albert
776myHandler = return $ addHeader 1797 albert
801```
777```
802
778
779+Note that the type of `addHeader x` is different than the type of `x`!
803
780
804−Serving static files
781+## Serving static files
805−====================
806
782
807*servant-server* also provides a way to just serve the content of a directory
783*servant-server* also provides a way to just serve the content of a directory
808under some path in your web API. As mentioned earlier in this document, the
784under some path in your web API. As mentioned earlier in this document, the
@@ -842,7 +818,9 @@ app3 :: Application
842app3 = serve codeAPI EmptyConfig server7
818app3 = serve codeAPI EmptyConfig server7
843```
819```
844
820
845−This server will match any request whose path starts with `/code` and will look for a file at the path described by the rest of the request path, inside the *tutorial/* directory of the path you run the program from.
821+This server will match any request whose path starts with `/code` and will look
822+for a file at the path described by the rest of the request path, inside the
823+ *tutorial/* directory of the path you run the program from.
846
824
847In other words:
825In other words:
848
826
@@ -941,8 +919,7 @@ $ curl http://localhost:8081/foo
941not found
919not found
942```
920```
943
921
944−Nested APIs
922+## Nested APIs
945−===========
946
923
947Let's see how you can define APIs in a modular way, while avoiding repetition. Consider this simple example:
924Let's see how you can define APIs in a modular way, while avoiding repetition. Consider this simple example:
948
925
@@ -1130,8 +1107,7 @@ serverFor = error "..."
1130-- or the mailing list if you get stuck!
1107-- or the mailing list if you get stuck!
1131```
1108```
1132
1109
1133−Using another monad for your handlers
1110+## Using another monad for your handlers
1134−=====================================
1135
1111
1136Remember how `Server` turns combinators for HTTP methods into `ExceptT ServantErr IO`? Well, actually, there's more to that. `Server` is actually a simple type synonym.
1112Remember how `Server` turns combinators for HTTP methods into `ExceptT ServantErr IO`? Well, actually, there's more to that. `Server` is actually a simple type synonym.
1137
1113
@@ -1143,8 +1119,7 @@ type Server api = ServerT api (ExceptT ServantErr IO)
1143
1119
1144The first and main question one might have then is: how do we write handlers that run in another monad? How can we "bring back" the value from a given monad into something *servant* can understand?
1120The first and main question one might have then is: how do we write handlers that run in another monad? How can we "bring back" the value from a given monad into something *servant* can understand?
1145
1121
1146−Natural transformations
1122+### Natural transformations
1147−-----------------------
1148
1123
1149If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1124If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1150do we have?
1125do we have?
@@ -1202,8 +1177,7 @@ We unfortunately can't use `readerServerT` as an argument of `serve`, because
1202`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT
1177`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT
1203ServantErr IO`. But there's a simple solution to this.
1178ServantErr IO`. But there's a simple solution to this.
1204
1179
1205−Enter `enter`
1180+### Enter `enter`
1206−-------------
1207
1181
1208That's right. We have just written `readerToEither`, which is exactly what we
1182That's right. We have just written `readerToEither`, which is exactly what we
1209would need to apply to the results of all handlers to make the handlers have the
1183would need to apply to the results of all handlers to make the handlers have the
@@ -1230,8 +1204,7 @@ $ curl http://localhost:8081/b
1230"hi"
1204"hi"
1231```
1205```
1232
1206
1233−Conclusion
1207+## Conclusion
1234−==========
1235
1208
1236You're now equipped to write any kind of webservice/web-application using *servant*. One thing not covered here is how to incorporate your own combinators and will be the topic of a page on the website. The rest of this document focuses on *servant-client*, *servant-jquery* and *servant-docs*.
1209You're now equipped to write any kind of webservice/web-application using *servant*. One thing not covered here is how to incorporate your own combinators and will be the topic of a page on the website. The rest of this document focuses on *servant-client*, *servant-jquery* and *servant-docs*.
1237
1210
doc/tutorial/tutorial.cabal+1−1
@@ -24,7 +24,7 @@ library
24 build-depends: base == 4.*
24 build-depends: base == 4.*
25 , base-compat
25 , base-compat
26 , text
26 , text
27− , aeson
27+ , aeson >= 0.11
28 , blaze-html
28 , blaze-html
29 , directory
29 , directory
30 , blaze-markup
30 , blaze-markup
stack.yaml+3
@@ -18,4 +18,7 @@ extra-deps:
18- engine-io-wai-1.0.2
18- engine-io-wai-1.0.2
19- control-monad-omega-0.3.1
19- control-monad-omega-0.3.1
20- should-not-typecheck-2.0.1
20- should-not-typecheck-2.0.1
21+- markdown-unlit-0.4.0
22+- aeson-0.11.0.0
23+- fail-4.9.0.0
21resolver: nightly-2015-10-08
24resolver: nightly-2015-10-08