packages feed

hunt-server-0.3.0.2: src/Hunt/Server/Schrotty.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs             #-}

-- ----------------------------------------------------------------------------
{- |
  Schrotty server.
  Scotty with custom error handling.
  All regular scotty functions can be used.
-}
-- ----------------------------------------------------------------------------

module Hunt.Server.Schrotty
(
  -- * Start the server
  schrotty
, schrottyOpts

  -- * Adjusted/Added Scotty Functions
, jsonData
, param
, jsonPretty

  -- * Types
, module Web.Scotty.Trans
, ActionSchrotty
, ActionSchrottyT
, WebError(..)
)
where

import           Network.HTTP.Types
import           Network.Wai.Handler.Warp   (Port)

import           Web.Scotty.Trans           hiding (jsonData, param)
import qualified Web.Scotty.Trans           as Scotty

import           Data.Text.Lazy             (Text)
import qualified Data.Text.Lazy             as TL

import           Data.Aeson                 as A
import           Data.Aeson.Encode.Pretty   as AP

import           Hunt.Interpreter.Command   (CmdError (..))
import           Hunt.Server.Common

-- ------------------------------------------------------------

type ActionSchrottyT m a = ActionT WebError m a

type ActionSchrotty a    = ActionT WebError IO a

data WebError
  = InterpreterError CmdError
  | NotFound
  | JsonInvalid      String
  | MissingParam     Text
  | Other            Text
  | Text             Int Text
  | forall a . ToJSON a =>
    Json             Int a
-- XXX: no Show instance because it's not a Haskell98 type

instance ScottyError WebError where
  stringError = Text 500 . TL.pack
  showError _ = "schrotty exception :("

-- ------------------------------------------------------------

handleCustomError :: Monad m => WebError -> ActionSchrottyT m ()
handleCustomError (NotFound)
  = do
    status status404
    text $ "Not found."
handleCustomError (JsonInvalid s)
  = do
    status status400
    jsonPretty $ JsonFailure 400 ("json invalid: " ++ s)
handleCustomError (MissingParam txt)
  = do
    status status404
    text $ "Query parameter missing " `TL.append` txt -- TODO: fix types
handleCustomError (Other txt)
  = do
    status status500
    text $ txt
handleCustomError (Text code bs)
  = do
    status (toEnum code)
    text $ bs
handleCustomError (Json code o)
  = do
    status (toEnum code)
    jsonPretty $ JsonFailure code o
handleCustomError (InterpreterError (ResError code msg))
  = handleCustomError (Json code msg)

-- | Like 'Web.Scotty.json', but pretty.
jsonPretty :: (ToJSON a, ScottyError e, Monad m) => a -> ActionT e m ()
jsonPretty v = do
  setHeader "Content-Type" "application/json"
  raw $ AP.encodePretty v

-- | Replacement for 'Web.Scotty.jsonData' with custom error.
jsonData :: (FromJSON a, Monad m) => ActionSchrottyT m a
jsonData = do
  b <- body
  case A.eitherDecode b of
    Right j -> return j
    Left e  -> raise $ JsonInvalid e

-- | Replacement for 'Web.Scotty.param' with custom error.
param :: (Parsable a, Monad m) => TL.Text -> ActionSchrottyT m a
param p = Scotty.param p
            `rescue` (\_ -> raise $ MissingParam p)

-- ------------------------------------------------------------

-- | Start schrotty.
schrotty :: Port -> ScottyT WebError IO () -> IO ()
schrotty p a = Scotty.scottyT p id id $ defaultHandler handleCustomError >> a

-- | Start schrotty with options.
schrottyOpts :: Options -> ScottyT WebError IO () -> IO ()
schrottyOpts o a = Scotty.scottyOptsT o id id $ defaultHandler handleCustomError >> a

-- ------------------------------------------------------------