-
-
Notifications
You must be signed in to change notification settings - Fork 413
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
26d4023
commit 1a6b30d
Showing
14 changed files
with
258 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Read the Docs configuration file for Sphinx projects | ||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details | ||
|
||
# Required | ||
version: 2 | ||
|
||
# Set the OS, Python version and other tools you might need | ||
build: | ||
os: ubuntu-22.04 | ||
tools: | ||
python: "3.12" | ||
# You can also specify other tool versions: | ||
# nodejs: "20" | ||
# rust: "1.70" | ||
# golang: "1.20" | ||
|
||
# Build documentation in the "docs/" directory with Sphinx | ||
sphinx: | ||
configuration: docs/conf.py | ||
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs | ||
# builder: "dirhtml" | ||
# Fail on all warnings to avoid broken references | ||
# fail_on_warning: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
# MultiVerb: Powerful endpoint types | ||
|
||
`MultiVerb` allows you to represent an API endpoint with multiple response types, status codes and headers. | ||
|
||
## Preliminaries | ||
|
||
```haskell | ||
{-# LANGUAGE GHC2021 #-} | ||
{-# LANGUAGE DataKinds #-} | ||
{-# LANGUAGE DerivingStrategies #-} | ||
{-# LANGUAGE DerivingVia #-} | ||
import GHC.Generics | ||
import Generics.SOP qualified as GSOP | ||
import Network.Wai.Handler.Warp as Warp | ||
import Servant.API | ||
import Servant.API.MultiVerb | ||
import Servant.Server | ||
import Servant.Server.Generic | ||
``` | ||
## Writing an endpoint | ||
Let us create an endpoint that captures an 'Int' and has the following logic: | ||
* If the number is negative, we return status code 400 and an empty body; | ||
* If the number is even, we return a 'Bool' in the response body; | ||
* If the number is odd, we return another 'Int' in the response body. | ||
Let us list all possible HTTP responses: | ||
```haskell | ||
type Responses = | ||
'[ RespondEmpty 400 "Negative" | ||
, Respond 200 "Odd number" Int | ||
, Respond 200 "Even number" Bool | ||
] | ||
``` | ||
Let us create the return type: | ||
```haskell | ||
data Result | ||
= NegativeNumber | ||
| Odd Int | ||
| Even Bool | ||
deriving stock (Generic) | ||
deriving (AsUnion Responses) | ||
via GenericAsUnion Responses Result | ||
instance GSOP.Generic Result | ||
``` | ||
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: | ||
``` | ||
• No instance for ‘AsConstructor | ||
((:) @Type Int ('[] @Type)) (Respond 200 "Even number" Bool)’ | ||
arising from the 'deriving' clause of a data type declaration | ||
``` | ||
> If you would prefer to write an intance of 'AsUnion' by yourself, read more in | ||
the “Implementing AsUnion manually” section. | ||
Finally, let us write our endpoint description: | ||
```haskell | ||
type MultipleChoicesInt = | ||
Capture "int" Int | ||
:> MultiVerb | ||
'GET | ||
'[JSON] | ||
Responses | ||
Result | ||
``` | ||
## Integration in a routing table | ||
We want to integrate our endpoint into a wider routing table with another | ||
endpoint: `version`, which returns the version of the API | ||
```haskell | ||
data Routes mode = Routes | ||
{ choicesRoutes :: mode :- "choices" :> Choices | ||
, version :: mode :- "version" :> Get '[JSON] Int | ||
} | ||
deriving stock (Generic) | ||
``` | ||
```haskell | ||
type Choices = NamedRoutes Choices' | ||
data Choices' mode = Choices' | ||
{ choices :: mode :- MultipleChoicesInt | ||
} | ||
deriving stock (Generic) | ||
choicesServer :: Choices' AsServer | ||
choicesServer = | ||
Choices' | ||
{ choices = choicesHandler | ||
} | ||
routesServer :: Routes AsServer | ||
routesServer = | ||
Routes | ||
{ choicesRoutes = choicesServer | ||
, version = versionHandler | ||
} | ||
choicesHandler :: Int -> Handler Result | ||
choicesHandler parameter = | ||
if parameter < 0 | ||
then pure NegativeNumber | ||
else | ||
if even parameter | ||
then pure $ Odd 3 | ||
else pure $ Even True | ||
versionHandler :: Handler Int | ||
versionHandler = pure 1 | ||
``` | ||
We can now plug everything together: | ||
```haskell | ||
main :: IO () | ||
main = do | ||
putStrLn "Starting server on http://localhost:5000" | ||
let server = genericServe routesServer | ||
Warp.run 5000 server | ||
``` | ||
Now let us run the server and observe how it behaves: | ||
``` | ||
$ http http://localhost:5000/version | ||
HTTP/1.1 200 OK | ||
Content-Type: application/json;charset=utf-8 | ||
Date: Thu, 29 Aug 2024 14:22:20 GMT | ||
Server: Warp/3.4.1 | ||
Transfer-Encoding: chunked | ||
1 | ||
``` | ||
``` | ||
$ http http://localhost:5000/choices/3 | ||
HTTP/1.1 200 OK | ||
Content-Type: application/json;charset=utf-8 | ||
Date: Thu, 29 Aug 2024 14:22:30 GMT | ||
Server: Warp/3.4.1 | ||
Transfer-Encoding: chunked | ||
true | ||
``` | ||
``` | ||
$ http http://localhost:5000/choices/2 | ||
HTTP/1.1 200 OK | ||
Content-Type: application/json;charset=utf-8 | ||
Date: Thu, 29 Aug 2024 14:22:33 GMT | ||
Server: Warp/3.4.1 | ||
Transfer-Encoding: chunked | ||
3 | ||
``` | ||
``` | ||
$ http http://localhost:5000/choices/-432 | ||
HTTP/1.1 400 Bad Request | ||
Date: Thu, 29 Aug 2024 14:22:41 GMT | ||
Server: Warp/3.4.1 | ||
Transfer-Encoding: chunked | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
cabal-version: 3.0 | ||
name: cookbook-multiverb | ||
version: 0.0.1 | ||
synopsis: MultiVerb cookbook | ||
homepage: http://docs.servant.dev/ | ||
license: BSD-3-Clause | ||
license-file: ../../../servant/LICENSE | ||
author: Servant Contributors | ||
maintainer: [email protected] | ||
category: Servant | ||
build-type: Simple | ||
|
||
executable cookbook-multiverb | ||
main-is: MultiVerb.lhs | ||
build-depends: base < 5 | ||
, aeson >= 2.2 | ||
, aeson-pretty >= 0.8.8 | ||
, async | ||
, http-client | ||
, mtl | ||
, servant | ||
, servant-client | ||
, generics-sop | ||
, sop-core | ||
, servant-server | ||
, servant-swagger | ||
, string-conversions | ||
, swagger2 | ||
, wai | ||
, warp | ||
default-language: Haskell2010 | ||
ghc-options: -Wall -pgmL markdown-unlit | ||
build-tool-depends: markdown-unlit:markdown-unlit | ||
|
Oops, something went wrong.