okapi: A microframework based on monadic parsing

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Please see the README on GitHub at https://github.com/githubuser/okapi#readme


[Skip to Readme]

Properties

Versions 0.1.0.0, 0.1.0.0, 0.1.0.1, 0.1.0.2
Change log ChangeLog.md
Dependencies aeson (>=2.0.3.0 && <2.1), base (>=4.7 && <5), base64 (>=0.4.2.3 && <0.5), bytestring (>=0.10.12.1 && <0.11), containers (>=0.6.4.1 && <0.7), http-api-data (>=0.4.3 && <0.5), http-types (>=0.12.3 && <0.13), lucid (>=2.11.0 && <2.12), mmorph (>=1.1.5 && <1.2), mtl (>=2.2.2 && <2.3), random (>=1.2.1 && <1.3), stm (>=2.5.0.0 && <2.6), text (>=1.2.5.0 && <1.3), transformers (>=0.5.6.2 && <0.6), unagi-chan (>=0.4.1.4 && <0.5), uuid (>=1.3.15 && <1.4), vault (>=0.3.1.5 && <0.4), wai (>=3.2.3 && <3.3), wai-extra (>=3.1.8 && <3.2), warp (>=3.3.20 && <3.4), warp-tls (>=3.3.2 && <3.4) [details]
License BSD-3-Clause
Copyright 2022 Monadic Systems LLC
Author Monadic Systems LLC
Maintainer tech@monadic.systems
Category Web
Home page https://github.com/githubuser/okapi#readme
Bug tracker https://github.com/githubuser/okapi/issues
Source repo head: git clone https://github.com/githubuser/okapi
Uploaded by rashad1030 at 2022-05-30T08:37:55Z

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for okapi-0.1.0.0

[back to package description]

Okapi

A microframework based on monadic parsing. Official documentation here.

Okapi mascot

Introduction

Okapi is a microframework for building web servers in Haskell based on monadic parsing. In contrast to other web frameworks in the Haskell ecosystem, Okapi is primarily concerned with being easy to understand and use, instead of extreme type safety. Here's an example of a simple web server:

{-# LANGUAGE OverloadedStrings #-}

import Data.Text
import Okapi

main :: IO ()
main = runOkapi id 3000 greet

greet = do
  seg "greet"
  name <- segParam
  respondPlainText [] $ "Hello " <> name <> "! I'm Okapi."

Running this code will start a server on localhost:3000. If you go to http://localhost:3000/greeting/Bob the server will respond with Hello Bob! I'm Okapi. in plain text format.

Okapi is a monadic parser for HTTP requests. This means it can be used with all Applicative, Alternative, and Monad typeclass methods, plus other Haskell idioms like parser combinators.

Here's a more complicated example that implements a calculator API. Type annotations are added to make the code easier to follow:

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import Control.Applicative ((<|>))
import Data.Aeson (ToJSON)
import Data.Text
import GHC.Generics (Generic)
import Okapi


main :: IO ()
main = runOkapi id 3000 calc

type Okapi a = OkapiT IO a

calc :: Okapi Response
calc = do
  get
  seg "calc"
  addOp <|> subOp <|> mulOp <|> divOp

addOp :: Okapi Response
addOp = do
  seg "add"
  (x, y) <- getArgs
  respondJSON [] $ x + y

subOp :: Okapi Response
subOp = do
  seg "sub" <|> seg "minus"
  (x, y) <- getArgs
  respondJSON [] $ x - y

mulOp :: Okapi Response
mulOp = do
  seg "mul"
  (x, y) <- getArgs
  respondJSON [] $ x * y

data DivResult = DivResult
  { answer :: Int,
    remainder :: Int
  }
  deriving (Eq, Show, Generic, ToJSON)

divOp :: Okapi Response
divOp = do
  seg "div"
  (x, y) <- getArgs
  if y == 0
    then error403 [] "Forbidden"
    else respondJSON [] $ DivResult {answer = x `div` y, remainder = x `mod` y}

getArgs :: Okapi (Int, Int)
getArgs = getArgsFromPath <|> getArgsFromQueryParams
  where
    getArgsFromPath :: Okapi (Int, Int)
    getArgsFromPath = do
      x <- segParamAs @Int
      y <- segParamAs @Int
      pure (x, y)

    getArgsFromQueryParams :: Okapi (Int, Int)
    getArgsFromQueryParams = do
      x <- queryParamAs @Int "x"
      y <- queryParamAs @Int "y"
      pure (x, y)

Okapi is very flexible. You could also define the above without do notation or type annotations:

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import Control.Applicative ((<|>))
import Control.Monad.Combinators (choice)
import Data.Aeson (ToJSON)
import Data.Text
import GHC.Generics (Generic)
import Okapi

main :: IO ()
main = runOkapi id 3000 calcNoDo

calcNoDo = get >> seg "calc" >> choice [addOp, subOp, mulOp, divOp]

addOp = seg "add" >> (getArgs >>= (\(x, y) -> respondJSON [] $ x + y))

subOp = (seg "sub" <|> seg "minus") >> (getArgs >>= (\(x, y) -> respondJSON [] $ x - y))

mulOp = seg "mul" >> (getArgs >>= (\(x, y) -> respondJSON [] $ x * y))

data DivResult = DivResult
  { answer :: Int,
    remainder :: Int
  }
  deriving (Eq, Show, Generic, ToJSON)

divOp = seg "div" >> (getArgs >>= (\(x, y) -> if y == 0 then error403 [] "Forbidden" else respondJSON [] $ DivResult (x `div` y) (x `mod` y)))

getArgs = getArgsFromPath <|> getArgsFromQueryParams
  where
    getArgsFromPath = segParamAs @Int >>= (\x -> segParamAs @Int >>= (\y -> pure (x, y)))
    getArgsFromQueryParams = queryParamAs @Int "x" >>= (\x -> queryParamAs @Int "y" >>= (\y -> pure (x, y)))

Don't like monads at all? You can use Okapi as an applicative parser too:

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import Control.Applicative (liftA2, (<|>))
import Control.Applicative.Combinators (choice)
import Data.Aeson (ToJSON)
import Data.Text
import GHC.Generics (Generic)
import Okapi

main :: IO ()
main = runOkapi id 3000 calcAp

calcAp = get *> seg "calc" *> choice [addOp, subOp, mulOp, divOp]

addOp = seg "add" *> respondJSONAp [] (uncurry (+) <$> getArgs)

subOp = (seg "sub" <|> seg "minus") *> respondJSONAp [] (uncurry (-) <$> getArgs)

mulOp = seg "mul" *> respondJSONAp [] (uncurry (*) <$> getArgs)

data DivResult = DivResult
  { answer :: Int,
    remainder :: Int
  }
  deriving (Eq, Show, Generic, ToJSON)

divOp = seg "div" *> (getArgs >>= (\(x, y) -> if y == 0 then error403 [] "Forbidden" else respondJSON [] $ DivResult (x `div` y) (x `mod` y)))

getArgs = getArgsFromPath <|> getArgsFromQueryParams
  where
    getArgsFromPath = liftA2 (,) (segParamAs @Int) (segParamAs @Int)
    getArgsFromQueryParams = liftA2 (,) (queryParamAs @Int "x") (queryParamAs @Int "y")

As you can see, Okapi's parsing functions are very modular and can be easily composed with one another to create parsing primitives tailored specifically to your needs. With Okapi, and the rest of the amazing Haskell ecosystem, you can create anything from simple website servers to complex APIs for web apps. All you need to get started is basic knowledge about the structure of HTTP requests and an idea of how monadic parsing works.

Contributing

Help is needed! There are issues and project board. Look there or open an issue or PR. There are no barriers. Any kind of help is welcome.