Skip to content

Commit 1a6b30d

Browse files
Cookbook
1 parent 26d4023 commit 1a6b30d

14 files changed

+258
-92
lines changed

.readthedocs.yml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Read the Docs configuration file for Sphinx projects
2+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3+
4+
# Required
5+
version: 2
6+
7+
# Set the OS, Python version and other tools you might need
8+
build:
9+
os: ubuntu-22.04
10+
tools:
11+
python: "3.12"
12+
# You can also specify other tool versions:
13+
# nodejs: "20"
14+
# rust: "1.70"
15+
# golang: "1.20"
16+
17+
# Build documentation in the "docs/" directory with Sphinx
18+
sphinx:
19+
configuration: docs/conf.py
20+
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
21+
# builder: "dirhtml"
22+
# Fail on all warnings to avoid broken references
23+
# fail_on_warning: true

cabal.project

+2-53
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,5 @@
1-
packages:
2-
servant/
3-
servant-auth/servant-auth
4-
servant-auth/servant-auth-client
5-
servant-auth/servant-auth-docs
6-
servant-auth/servant-auth-server
7-
servant-auth/servant-auth-swagger
8-
9-
servant-client-core/
10-
servant-client/
11-
servant-docs/
12-
servant-foreign/
13-
servant-http-streams/
14-
servant-quickcheck/
15-
servant-server/
16-
servant-swagger/
17-
18-
doc/tutorial/
19-
20-
-- servant streaming
21-
packages:
22-
servant-machines/
23-
servant-conduit/
24-
servant-pipes/
25-
26-
-- servant GHCJS
27-
-- packages:
28-
-- servant-jsaddle/
29-
30-
-- Cookbooks
31-
packages:
32-
doc/cookbook/basic-auth
33-
doc/cookbook/curl-mock
34-
doc/cookbook/custom-errors
35-
doc/cookbook/basic-streaming
36-
doc/cookbook/db-postgres-pool
37-
doc/cookbook/db-sqlite-simple
38-
doc/cookbook/file-upload
39-
doc/cookbook/generic
40-
doc/cookbook/hoist-server-with-context
41-
doc/cookbook/https
42-
doc/cookbook/jwt-and-basic-auth
43-
doc/cookbook/pagination
44-
-- doc/cookbook/sentry
45-
-- Commented out because servant-quickcheck currently doesn't build.
46-
-- doc/cookbook/testing
47-
doc/cookbook/uverb
48-
doc/cookbook/structuring-apis
49-
doc/cookbook/using-custom-monad
50-
doc/cookbook/using-free-client
51-
-- doc/cookbook/open-id-connect
52-
doc/cookbook/managed-resource
53-
doc/cookbook/openapi3
1+
import: ./cabal/libraries/cabal.config
2+
import: ./cabal/cookbook/cabal.config
543

554
tests: True
565
optimization: False

doc/conf.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@
3939
# The suffix(es) of source filenames.
4040
# You can specify multiple suffix as a list of string:
4141
#
42-
source_suffix = ['.rst', '.md', '.lhs']
42+
source_suffix = {
43+
'.rst': 'restructuredtext',
44+
'.md': 'markdown',
45+
'.lhs': 'markdown',
46+
}
47+
4348

4449
# The master toctree document.
4550
master_doc = 'index'
@@ -63,7 +68,7 @@
6368
#
6469
# This is also used if you do content translation via gettext catalogs.
6570
# Usually you set "language" from the command line for these cases.
66-
language = None
71+
language = 'en'
6772

6873
# List of patterns, relative to source directory, that match files and
6974
# directories to ignore when looking for source files.
@@ -166,6 +171,4 @@
166171

167172
# -- Markdown -------------------------------------------------------------
168173

169-
source_parsers = {
170-
'.lhs': CommonMarkParser,
171-
}
174+
extensions.append('recommonmark')

doc/cookbook/basic-streaming/Streaming.lhs

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ main = do
123123
go !acc (S.Yield _ s) = go (acc + 1) s
124124
_ -> do
125125
putStrLn "Try:"
126-
putStrLn "cabal new-run cookbook-basic-streaming server"
127-
putStrLn "cabal new-run cookbook-basic-streaming client 10"
126+
putStrLn "cabal run cookbook-basic-streaming server"
127+
putStrLn "cabal run cookbook-basic-streaming client 10"
128128
putStrLn "time curl -H 'Accept: application/json' localhost:8000/slow/5"
129129
```

doc/cookbook/cabal.project

-22
This file was deleted.

doc/cookbook/cabal.project.local

Whitespace-only changes.

doc/cookbook/file-upload/FileUpload.lhs

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ main = withSocketsDo . bracket (forkIO startServer) killThread $ \_threadid -> d
113113
If you run this, you should get:
114114
115115
``` bash
116-
$ cabal new-build cookbook-file-upload
116+
$ cabal build cookbook-file-upload
117117
[...]
118118
$ dist-newstyle/build/x86_64-linux/ghc-8.2.1/cookbook-file-upload-0.1/x/cookbook-file-upload/build/cookbook-file-upload/cookbook-file-upload
119119
Inputs:

doc/cookbook/generic/Generic.lhs

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ main = do
107107
("run-custom-monad":_) -> do
108108
putStrLn "Starting cookbook-generic with a custom monad at http://localhost:8000"
109109
run 8000 (appMyMonad AppCustomState)
110-
_ -> putStrLn "To run, pass 'run' argument: cabal new-run cookbook-generic run"
110+
_ -> putStrLn "To run, pass 'run' argument: cabal run cookbook-generic run"
111111
```
112112
113113
## Using generics together with a custom monad

doc/cookbook/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ you name it!
2828
using-free-client/UsingFreeClient.lhs
2929
custom-errors/CustomErrors.lhs
3030
uverb/UVerb.lhs
31+
multiverb/MultiVerb.lhs
3132
basic-auth/BasicAuth.lhs
3233
basic-streaming/Streaming.lhs
3334
jwt-and-basic-auth/JWTAndBasicAuth.lhs

doc/cookbook/multiverb/MultiVerb.lhs

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# MultiVerb: Powerful endpoint types
2+
3+
`MultiVerb` allows you to represent an API endpoint with multiple response types, status codes and headers.
4+
5+
## Preliminaries
6+
7+
```haskell
8+
{-# LANGUAGE GHC2021 #-}
9+
{-# LANGUAGE DataKinds #-}
10+
{-# LANGUAGE DerivingStrategies #-}
11+
{-# LANGUAGE DerivingVia #-}
12+
13+
import GHC.Generics
14+
import Generics.SOP qualified as GSOP
15+
import Network.Wai.Handler.Warp as Warp
16+
17+
import Servant.API
18+
import Servant.API.MultiVerb
19+
import Servant.Server
20+
import Servant.Server.Generic
21+
```
22+
23+
## Writing an endpoint
24+
25+
Let us create an endpoint that captures an 'Int' and has the following logic:
26+
27+
* If the number is negative, we return status code 400 and an empty body;
28+
* If the number is even, we return a 'Bool' in the response body;
29+
* If the number is odd, we return another 'Int' in the response body.
30+
31+
32+
Let us list all possible HTTP responses:
33+
```haskell
34+
35+
type Responses =
36+
'[ RespondEmpty 400 "Negative"
37+
, Respond 200 "Odd number" Int
38+
, Respond 200 "Even number" Bool
39+
]
40+
```
41+
42+
Let us create the return type:
43+
44+
```haskell
45+
data Result
46+
= NegativeNumber
47+
| Odd Int
48+
| Even Bool
49+
deriving stock (Generic)
50+
deriving (AsUnion Responses)
51+
via GenericAsUnion Responses Result
52+
53+
instance GSOP.Generic Result
54+
```
55+
56+
These deriving statements above tie together the responses and the return values, and the order in which they are defined matters. For instance, if `Even` and `Odd` had switched places in the definition of `Result`, this would provoke an error:
57+
58+
```
59+
• No instance for ‘AsConstructor
60+
((:) @Type Int ('[] @Type)) (Respond 200 "Even number" Bool)’
61+
arising from the 'deriving' clause of a data type declaration
62+
```
63+
64+
> If you would prefer to write an intance of 'AsUnion' by yourself, read more in
65+
the “Implementing AsUnion manually” section.
66+
67+
Finally, let us write our endpoint description:
68+
69+
```haskell
70+
type MultipleChoicesInt =
71+
Capture "int" Int
72+
:> MultiVerb
73+
'GET
74+
'[JSON]
75+
Responses
76+
Result
77+
```
78+
79+
## Integration in a routing table
80+
81+
We want to integrate our endpoint into a wider routing table with another
82+
endpoint: `version`, which returns the version of the API
83+
84+
```haskell
85+
data Routes mode = Routes
86+
{ choicesRoutes :: mode :- "choices" :> Choices
87+
, version :: mode :- "version" :> Get '[JSON] Int
88+
}
89+
deriving stock (Generic)
90+
```
91+
92+
```haskell
93+
type Choices = NamedRoutes Choices'
94+
data Choices' mode = Choices'
95+
{ choices :: mode :- MultipleChoicesInt
96+
}
97+
deriving stock (Generic)
98+
99+
choicesServer :: Choices' AsServer
100+
choicesServer =
101+
Choices'
102+
{ choices = choicesHandler
103+
}
104+
105+
routesServer :: Routes AsServer
106+
routesServer =
107+
Routes
108+
{ choicesRoutes = choicesServer
109+
, version = versionHandler
110+
}
111+
112+
choicesHandler :: Int -> Handler Result
113+
choicesHandler parameter =
114+
if parameter < 0
115+
then pure NegativeNumber
116+
else
117+
if even parameter
118+
then pure $ Odd 3
119+
else pure $ Even True
120+
121+
versionHandler :: Handler Int
122+
versionHandler = pure 1
123+
```
124+
125+
We can now plug everything together:
126+
127+
128+
```haskell
129+
main :: IO ()
130+
main = do
131+
putStrLn "Starting server on http://localhost:5000"
132+
let server = genericServe routesServer
133+
Warp.run 5000 server
134+
```
135+
136+
Now let us run the server and observe how it behaves:
137+
138+
```
139+
$ http http://localhost:5000/version
140+
HTTP/1.1 200 OK
141+
Content-Type: application/json;charset=utf-8
142+
Date: Thu, 29 Aug 2024 14:22:20 GMT
143+
Server: Warp/3.4.1
144+
Transfer-Encoding: chunked
145+
146+
1
147+
```
148+
149+
150+
```
151+
$ http http://localhost:5000/choices/3
152+
HTTP/1.1 200 OK
153+
Content-Type: application/json;charset=utf-8
154+
Date: Thu, 29 Aug 2024 14:22:30 GMT
155+
Server: Warp/3.4.1
156+
Transfer-Encoding: chunked
157+
158+
true
159+
```
160+
161+
```
162+
$ http http://localhost:5000/choices/2
163+
HTTP/1.1 200 OK
164+
Content-Type: application/json;charset=utf-8
165+
Date: Thu, 29 Aug 2024 14:22:33 GMT
166+
Server: Warp/3.4.1
167+
Transfer-Encoding: chunked
168+
169+
3
170+
```
171+
172+
```
173+
$ http http://localhost:5000/choices/-432
174+
HTTP/1.1 400 Bad Request
175+
Date: Thu, 29 Aug 2024 14:22:41 GMT
176+
Server: Warp/3.4.1
177+
Transfer-Encoding: chunked
178+
```
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
cabal-version: 3.0
2+
name: cookbook-multiverb
3+
version: 0.0.1
4+
synopsis: MultiVerb cookbook
5+
homepage: http://docs.servant.dev/
6+
license: BSD-3-Clause
7+
license-file: ../../../servant/LICENSE
8+
author: Servant Contributors
9+
maintainer: [email protected]
10+
category: Servant
11+
build-type: Simple
12+
13+
executable cookbook-multiverb
14+
main-is: MultiVerb.lhs
15+
build-depends: base < 5
16+
, aeson >= 2.2
17+
, aeson-pretty >= 0.8.8
18+
, async
19+
, http-client
20+
, mtl
21+
, servant
22+
, servant-client
23+
, generics-sop
24+
, sop-core
25+
, servant-server
26+
, servant-swagger
27+
, string-conversions
28+
, swagger2
29+
, wai
30+
, warp
31+
default-language: Haskell2010
32+
ghc-options: -Wall -pgmL markdown-unlit
33+
build-tool-depends: markdown-unlit:markdown-unlit
34+

0 commit comments

Comments
 (0)