git.iko.soy/typeable/servant
on master help sign in
sundry tutorial improvements
commite1312c1bb6abb8e89bbfff30122db74ad8cb2e39 authorJulian K. Arni <jkarni@gmail.com> dateThu 18 Feb 2016, 00:39 (11 years ago) parent9d2d710
5 files changed+75−94
doc/conf.py+5 doc/requirements.txt+1−1 doc/tutorial/Server.lhs+65−92 doc/tutorial/tutorial.cabal+1−1 stack.yaml+3
doc/conf.py+5
@@ -94,6 +94,11 @@ exclude_patterns = ['_build', 'venv']
9494# The name of the Pygments (syntax highlighting) style to use.
9595pygments_style = 'sphinx'
9696
97+def setup(app):
98+ from sphinx.highlighting import lexers
99+ from pygments.lexers import HaskellLexer
100+ lexers['haskell ignore'] = HaskellLexer(stripnl=False)
101+
97102# A list of ignored prefixes for module index sorting.
98103#modindex_common_prefix = []
99104
doc/requirements.txt+1−1
@@ -10,7 +10,7 @@ Jinja2==2.8
1010livereload==2.4.1
1111MarkupSafe==0.23
1212pathtools==0.1.2
13−Pygments==2.1
13+Pygments==2.1.1
1414pytz==2015.7
1515PyYAML==3.11
1616recommonmark==0.4.0
doc/tutorial/Server.lhs+65−92
@@ -23,8 +23,7 @@ Usage: tutorial N
2323 where N is the number of the example you want to run.
2424```
2525
26−A first example
27−===============
26+## A first example
2827
2928Equipped with some basic knowledge about the way we represent API, let's now write our first webservice.
3029
@@ -53,7 +52,6 @@ import Data.Aeson
5352import Data.Aeson.Types
5453import Data.Attoparsec.ByteString
5554import Data.ByteString (ByteString)
56−import Data.Int
5755import Data.List
5856import Data.String.Conversions
5957import Data.Time.Calendar
@@ -97,7 +95,7 @@ data User = User
9795 { name :: String
9896 , age :: Int
9997 , email :: String
100− , registration_date :: Day
98+ , registrationDate :: Day
10199 } deriving (Eq, Show, Generic)
102100
103101instance ToJSON User
@@ -179,8 +177,7 @@ $ curl http://localhost:8081/users
179177[{"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"}]
180178```
181179
182−More endpoints
183−==============
180+## More endpoints
184181
185182What if we want more than one endpoint? Let's add `/albert` and `/isaac` to view the corresponding users encoded in JSON.
186183
@@ -218,8 +215,7 @@ And that's it! You can run this example with
218215`dist/build/tutorial/tutorial 2` and check out the data available
219216at `/users`, `/albert` and `/isaac`.
220217
221−From combinators to handler arguments
222−=====================================
218+## From combinators to handler arguments
223219
224220Fine, we can write trivial webservices easily, but none of the two above use
225221any "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
237233 :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
238234
239235data Position = Position
240− { x :: Int
241− , y :: Int
236+ { xCoord :: Int
237+ , yCoord :: Int
242238 } deriving Generic
243239
244240instance ToJSON Position
@@ -334,8 +330,7 @@ that get turned into arguments to the handlers, the type of the argument.
334330 > - `QueryParams "something" a` and `MatrixParams "something" a` get turned into arguments of type `[a]`.
335331 > - `ReqBody contentTypes a` gets turned into an argument of type `a`.
336332
337−The `FromText`/`ToText` classes
338−===============================
333+## The `FromHttpApiData`/`ToHttpApiData` classes
339334
340335Wait... How does *servant* know how to decode the `Int`s from the URL? Or how
341336to decode a `ClientInfo` value from the request body? This is what this and the
@@ -343,54 +338,42 @@ following two sections address.
343338
344339`Capture`s and `QueryParam`s are represented by some textual value in URLs.
345340`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
347−decided to provide a pair of typeclasses, `FromText` and `ToText` which just
348−let you say that you can respectively *extract* or *encode* values of some type
349−*from*/*to* text. Here are the definitions:
341+corresponding (textual) value in the request's "metadata". How types are
342+decoded from headers, captures, and query params is expressed in a class
343+`FromHttpApiData` (from the package
344+[*http-api-data*](http://hackage.haskell.org/package/http-api-data)):
350345
351346``` haskell ignore
352−class FromText a where
353− fromText :: Text -> Maybe a
354−
355−class ToText a where
356− toText :: a -> Text
357−```
347+class FromHttpApiData a where
348+ {-# MINIMAL parseUrlPiece | parseQueryParam #-}
349+ -- | Parse URL path piece.
350+ parseUrlPiece :: Text -> Either Text a
351+ parseUrlPiece = parseQueryParam
358352
359−And as long as the type that a `Capture`/`QueryParam`/`Header`/etc will be
360−decoded to provides a `FromText` instance, it will Just Work. *servant*
361−provides a decent number of instances, but here are some examples of defining
362−your own.
353+ -- | Parse HTTP header value.
354+ parseHeader :: ByteString -> Either Text a
355+ parseHeader = parseUrlPiece . decodeUtf8
363356
364−``` haskell
365−-- A typical enumeration
366−data Direction
367− = Up
368− | Down
369− | Left
370− | Right
371−
372−newtype UserId = UserId Int64
357+ -- | Parse query param value.
358+ parseQueryParam :: Text -> Either Text a
359+ parseQueryParam = parseUrlPiece
373360```
374361
375−or writing the instances by hand:
376−
377−``` haskell ignore
378−instance FromText UserId where
379− fromText = fmap UserId fromText
362+As you can see, as long as you provide either `parseUrlPiece` (for `Capture`s)
363+or `parseQueryParam` (for `QueryParam`s), the other methods will be defined in
364+terms of this.
380365
381−instance ToText UserId where
382− toText (UserId i) = toText i
383−```
366+*http-api-data* provides a decent number of instances, helpers for defining new
367+ones, and wonderful documentation.
384368
385369There's not much else to say about these classes. You will need instances for
386−them when using `Capture`, `QueryParam`, `QueryParams`, `MatrixParam`,
387−`MatrixParams` and `Header` with your types. You will need `FromText` instances
388−for server-side request handlers and `ToText` instances only when using
370+them when using `Capture`, `QueryParam`, `QueryParams`, and `Header` with your
371+types. You will need `FromHttpApiData` instances for server-side request
372+handlers and `ToHttpApiData` instances only when using
389373*servant-client*, as described in the [section about deriving haskell
390374functions to query an API](/tutorial/client.html).
391375
392−Using content-types with your data types
393−========================================
376+## Using content-types with your data types
394377
395378The same principle was operating when decoding request bodies from JSON, and
396379responses *into* JSON. (JSON is just the running example - you can do this with
@@ -399,8 +382,8 @@ any content-type.)
399382This section introduces a couple of typeclasses provided by *servant* that make
400383all of this work.
401384
402−The truth behind `JSON`
403−-----------------------
385+### The truth behind `JSON`
386+
404387
405388What exactly is `JSON`? Like the 3 other content types provided out of the box
406389by *servant*, it's a really dumb data type.
@@ -464,8 +447,6 @@ And now the `MimeUnrender` class, which lets us extract values from lazy
464447``` haskell ignore
465448class Accept ctype => MimeUnrender ctype a where
466449 mimeUnrender :: Proxy ctype -> ByteString -> Either String a
467− -- alternatively:
468− mimeUnrender :: Proxy ctype -> (ByteString -> Either String a)
469450```
470451
471452We 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
496477document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/), simply by adding `Accept:
497478text/html` to their request headers.
498479
499−Case-studies: *servant-blaze* and *servant-lucid*
500−-------------------------------------------------
480+### Case-studies: *servant-blaze* and *servant-lucid*
501481
502482These days, most of the haskellers who write their HTML UIs directly from
503483Haskell use either [blaze-html](http://hackage.haskell.org/package/blaze-html)
@@ -615,8 +595,8 @@ instance ToHtml [Person] where
615595We create some `Person` values and serve them as a list:
616596
617597``` haskell
618−persons :: [Person]
619−persons =
598+people :: [Person]
599+people =
620600 [ Person "Isaac" "Newton"
621601 , Person "Albert" "Einstein"
622602 ]
@@ -625,7 +605,7 @@ personAPI :: Proxy PersonAPI
625605personAPI = Proxy
626606
627607server4 :: Server PersonAPI
628−server4 = return persons
608+server4 = return people
629609
630610app2 :: Application
631611app2 = serve personAPI EmptyConfig server4
@@ -641,8 +621,7 @@ And we're good to go. You can run this example with `dist/build/tutorial/tutoria
641621 # or just point your browser to http://localhost:8081/persons
642622```
643623
644−The `ExceptT ServantErr IO` monad
645−=================================
624+## The `ExceptT ServantErr IO` monad
646625
647626At the heart of the handlers is the monad they run in, namely `ExceptT
648627ServantErr IO`. One might wonder: why this monad? The answer is that it is the
@@ -660,40 +639,39 @@ Let's recall some definitions.
660639-- from the Prelude
661640data Either e a = Left e | Right a
662641
663−-- from the 'either' 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
666− = ExceptT { runEitherT :: m (Either e a) }
642+-- from the 'mtl' package at
643+newtype ExceptT e m a = ExceptT ( m (Either e a) )
667644```
668645
669646In short, this means that a handler of type `ExceptT ServantErr IO a` is simply
670647equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
671648action that either returns an error or a result.
672649
673−The aforementioned `either` package is worth taking a look at. Perhaps most
674−importantly:
675−
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
680−return a success).
650+The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)
651+from which `ExceptT` comes is worth looking at.
652+Perhaps most importantly, `ExceptT` is an instance of `MonadError`, so
653+`throwError` can be used to return an error from your handler (whereas `return`
654+ is enough to return a success).
681655
682656Most of what you'll be doing in your handlers is running some IO and,
683657depending on the result, you might sometimes want to throw an error of some
684658kind and abort early. The next two sections cover how to do just that.
685659
686−Performing IO
687−-------------
660+### Performing IO
688661
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:
662+Another important instance from the list above is `MonadIO m => MonadIO
663+(ExceptT e m)`.
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:
690666
691667``` haskell ignore
692668class Monad m => MonadIO m where
693669 liftIO :: IO a -> m a
694670```
695671
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`:
697675
698676``` haskell
699677type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
@@ -710,8 +688,7 @@ server5 = do
710688 return (FileContent filecontent)
711689```
712690
713−Failing, through `ServantErr`
714−-----------------------------
691+### Failing, through `ServantErr`
715692
716693If you want to explicitly fail at providing the result promised by an endpoint
717694using 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.
787764 {"content":"Hello\n"}
788765```
789766
790−Response headers
791−================
767+## Response headers
792768
793769To add headers to your response, use [addHeader](http://hackage.haskell.org/package/servant-0.4.4/docs/Servant-API-ResponseHeaders.html).
794770Note that this changes the type of your API, as we can see in the following example:
@@ -800,9 +776,9 @@ myHandler :: Server MyHandler
800776myHandler = return $ addHeader 1797 albert
801777```
802778
779+Note that the type of `addHeader x` is different than the type of `x`!
803780
804−Serving static files
805−====================
781+## Serving static files
806782
807783*servant-server* also provides a way to just serve the content of a directory
808784under some path in your web API. As mentioned earlier in this document, the
@@ -842,7 +818,9 @@ app3 :: Application
842818app3 = serve codeAPI EmptyConfig server7
843819```
844820
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.
846824
847825In other words:
848826
@@ -941,8 +919,7 @@ $ curl http://localhost:8081/foo
941919not found
942920```
943921
944−Nested APIs
945−===========
922+## Nested APIs
946923
947924Let's see how you can define APIs in a modular way, while avoiding repetition. Consider this simple example:
948925
@@ -1130,8 +1107,7 @@ serverFor = error "..."
11301107-- or the mailing list if you get stuck!
11311108```
11321109
1133−Using another monad for your handlers
1134−=====================================
1110+## Using another monad for your handlers
11351111
11361112Remember 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.
11371113
@@ -1143,8 +1119,7 @@ type Server api = ServerT api (ExceptT ServantErr IO)
11431119
11441120The 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?
11451121
1146−Natural transformations
1147−-----------------------
1122+### Natural transformations
11481123
11491124If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
11501125do we have?
@@ -1202,8 +1177,7 @@ We unfortunately can't use `readerServerT` as an argument of `serve`, because
12021177`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT
12031178ServantErr IO`. But there's a simple solution to this.
12041179
1205−Enter `enter`
1206−-------------
1180+### Enter `enter`
12071181
12081182That's right. We have just written `readerToEither`, which is exactly what we
12091183would need to apply to the results of all handlers to make the handlers have the
@@ -1230,8 +1204,7 @@ $ curl http://localhost:8081/b
12301204"hi"
12311205```
12321206
1233−Conclusion
1234−==========
1207+## Conclusion
12351208
12361209You'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*.
12371210
doc/tutorial/tutorial.cabal+1−1
@@ -24,7 +24,7 @@ library
2424 build-depends: base == 4.*
2525 , base-compat
2626 , text
27− , aeson
27+ , aeson >= 0.11
2828 , blaze-html
2929 , directory
3030 , blaze-markup
stack.yaml+3
@@ -18,4 +18,7 @@ extra-deps:
1818- engine-io-wai-1.0.2
1919- control-monad-omega-0.3.1
2020- should-not-typecheck-2.0.1
21+- markdown-unlit-0.4.0
22+- aeson-0.11.0.0
23+- fail-4.9.0.0
2124resolver: nightly-2015-10-08