packages feed

tianbar-1.0.0.0: src/System/Tianbar/RequestResponse.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module System.Tianbar.RequestResponse (
    URI (..),
    FromData (..),
    Response (..),
    bytestringResponse,
    jsonResponse,
    look,
    looks,
    okResponse,
    parseURI,
    serveFile,
    stringResponse,
    textResponse,
) where

import Control.Monad.IO.Class
import Control.Monad.Reader

import Data.Aeson

import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.ByteString.UTF8 as U

import Data.Maybe

import qualified Data.Text as T
import qualified Data.Text.Encoding as TE

import Network.HTTP.Types (Query, decodePath)
import Network.Mime (defaultMimeLookup)

import System.FilePath


data URI = URI { uriPathSegments :: [T.Text]
               , uriQuery :: Query
               }
               deriving (Show)

parseURI :: T.Text -> URI
parseURI str = URI segments query
    where (segments, query) = decodePath uriPath
          uriPath = extractPath $ TE.encodeUtf8 str
          prefix = U.fromString "tianbar://"
          extractPath uri | prefix `B.isPrefixOf` uri = B.drop (B.length prefix) uri
                          | otherwise = uri

data Response = Response { content :: B.ByteString
                         , mimeType :: Maybe String
                         }

stringResponse :: Monad m => String -> m Response
stringResponse = bytestringResponse . U.fromString

textResponse :: Monad m => T.Text -> m Response
textResponse = bytestringResponse . TE.encodeUtf8

bytestringResponse :: Monad m => B.ByteString -> m Response
bytestringResponse str = return $ Response str (Just "text/plain")

okResponse :: Monad m => m Response
okResponse = bytestringResponse "ok"

jsonResponse :: (Monad m, ToJSON v) => v -> m Response
jsonResponse = bytestringResponse . LBS.toStrict . encode

serveFile :: MonadIO m => FilePath -> m Response
serveFile filePath = do
    contents <- liftIO $ B.readFile filePath
    let fileType = defaultMimeLookup $ T.pack $ takeFileName filePath
    -- FIXME: Guess the MIME type
    return $ Response contents (Just $ U.toString fileType)

look :: (MonadPlus m, MonadReader URI m) => String -> m String
look param = looks param >>= \values -> case values of
               [value] -> return value
               _ -> mzero

looks :: (MonadPlus m, MonadReader URI m) => String -> m [String]
looks param = asks uriQuery >>= \params -> do
    let paramTxt = U.fromString param
    let isParam (p, value) | p == paramTxt = fmap U.toString value
                           | otherwise = Nothing
    return $ mapMaybe isParam params

class FromData a where
    fromData :: (MonadPlus m, MonadReader URI m) => m a

instance FromData a => FromData (Maybe a) where
    fromData = fmap Just fromData `mplus` return Nothing