mirror of
https://github.com/monadicsystems/okapi.git
synced 2024-11-23 09:54:24 +03:00
108 lines
2.8 KiB
Markdown
108 lines
2.8 KiB
Markdown
|
# 🦓🦒Okapi
|
||
|
|
||
|
Okapi is a micro framework for implementing HTTP servers.
|
||
|
|
||
|
- Ergonomic DSL for routing and parsing requests
|
||
|
- Integrate Okapi with ANY monad stack or effect system
|
||
|
- Automatically generate clients and OpenAPI specifications
|
||
|
|
||
|
The primary way to implement a server in Okapi is via *Endpoints*:
|
||
|
|
||
|
```haskell
|
||
|
-- | Define Endpoints using an Applicative eDSL
|
||
|
myEndpoint = Endpoint
|
||
|
GET
|
||
|
do
|
||
|
Path.static "index"
|
||
|
magicNumber <- Path.param @Int
|
||
|
pure magicNumber
|
||
|
do
|
||
|
x <- Query.param @Int "x"
|
||
|
y <- Query.option 10 $ Query.param @Int "y"
|
||
|
pure (x, y)
|
||
|
do
|
||
|
foo <- Body.json @Value
|
||
|
pure foo
|
||
|
do pure ()
|
||
|
do
|
||
|
itsOk <- Responder.json @Int HTTP.status200 do
|
||
|
addSecretNumber <- ResponderHeaders.has @Int "X-SECRET"
|
||
|
pure addSecretNumber
|
||
|
pure itsOk
|
||
|
|
||
|
-- | Define Handlers for Endpoints
|
||
|
myHandler magicNumber (x, y) foo () responder = do
|
||
|
let newNumber = magicNumber + x * y
|
||
|
print newNumber
|
||
|
print foo
|
||
|
return $ responder (\addHeader response -> addHeader (newNumber * 100) response) newNumber
|
||
|
|
||
|
-- | Combine Endpoints with Handlers to create Plans
|
||
|
myPlan = Plan
|
||
|
myEndpoint
|
||
|
myHandler
|
||
|
|
||
|
-- | Compose Plans with other Plans to create Servers
|
||
|
myServer = Server
|
||
|
Nothing
|
||
|
default404
|
||
|
[ myPlan
|
||
|
, Plan myOtherEndpoint myOtherHandler
|
||
|
, anotherPlan
|
||
|
]
|
||
|
|
||
|
-- | Run your Servers using Warp
|
||
|
main = Warp.run 3000 $ instantiate id myServer
|
||
|
```
|
||
|
|
||
|
An alternative, more concise way of defining a server in Okapi is via *Matchpoints*:
|
||
|
|
||
|
```haskell
|
||
|
-- | Define Matchpoint patterns using PatternSynonyms,
|
||
|
-- ViewPatterns, and the same eDSL used for Endpoints
|
||
|
pattern GetUsers optF <- Matchpoint
|
||
|
GET
|
||
|
["users"]
|
||
|
_
|
||
|
(Body.eval $ Body.optional $ Body.json @Filter -> Ok optF)
|
||
|
_
|
||
|
|
||
|
pattern AddUser user <- Matchpoint
|
||
|
POST -- Method
|
||
|
["users"] -- Path
|
||
|
_ -- Query
|
||
|
(Body.eval $ Body.json @User -> Ok user) -- Body
|
||
|
_ -- Headers
|
||
|
|
||
|
pattern GetUsersByID userID <- Matchpoint
|
||
|
GET
|
||
|
(Path.eval $ Path.static "users" *> Path.param @UserID -> Ok userID)
|
||
|
_
|
||
|
""
|
||
|
_
|
||
|
|
||
|
-- | Servers are just contextful functions from a Matchpoint to a Response
|
||
|
type Server m = Matchpoint -> m Response
|
||
|
|
||
|
myServer :: Server IO
|
||
|
myServer = \case
|
||
|
GetUser -> do
|
||
|
...
|
||
|
GetUserByID userID -> do
|
||
|
...
|
||
|
AddUser user -> do
|
||
|
...
|
||
|
_ -> do
|
||
|
...
|
||
|
|
||
|
-- | Run your Server using Warp
|
||
|
main = Warp.run 3000 $ instantiate id myServer
|
||
|
```
|
||
|
|
||
|
The advantadge of using Endpoints over Matchpoints is that Okapi can
|
||
|
automatically generate specifications and clients for a server implemented
|
||
|
with Endpoints, but not a server implemented with Matchpoints.
|
||
|
|
||
|
On the flip side, a server implemented with Matchpoints will be more
|
||
|
concise than a server implemented with Endpoints.
|