----------------------------------------------------------------------------- -- | -- Module : Network.Waitra -- Copyright : (c) 2015 Futurice -- License : MIT (see the file LICENSE) -- Maintainer : Oleg Grenrus -- Stability : experimental -- -- @Network.Waitra@ is a very simple router. -- It's useful for writing simple API web-services, -- when you don't want to use the whole Yesod stack. ---------------------------------------------------------------------------- module Network.Waitra ( -- * Types Path , Route(..) -- * Static paths routes , simpleRoute , simpleGet , simplePost , simplePut , simpleDelete -- * Regex paths routes , routeGet , routePost , routePut , routeDelete -- * JSON helper , jsonApp , jsonApp' -- * Compilation , routeMiddleware , waitraMiddleware -- * Re-exports , module Text.Regex.Applicative , module Network.HTTP.Types.Status ) where import Data.Aeson import Data.String (fromString) import qualified Data.Text as T import qualified Network.HTTP.Types as H import Network.HTTP.Types.Status import Network.Wai import Text.Regex.Applicative -- | We use strings, as - unluckily - `Text.Regex.Applicative` doesn't work with `Text` directly. type Path = String data Route = Route H.Method (RE Char Application) simpleRoute :: H.Method -> Path -> Application -> Route simpleRoute method r app = Route method (const app <$> string r) simpleGet :: Path -> Application -> Route simpleGet = simpleRoute H.methodGet simplePost :: Path -> Application -> Route simplePost = simpleRoute H.methodPost simplePut :: Path -> Application -> Route simplePut = simpleRoute H.methodPut simpleDelete :: Path -> Application -> Route simpleDelete = simpleRoute H.methodDelete routeGet :: RE Char Application -> Route routeGet = Route H.methodGet routePost :: RE Char Application -> Route routePost = Route H.methodPost routeDelete :: RE Char Application -> Route routeDelete = Route H.methodDelete routePut :: RE Char Application -> Route routePut = Route H.methodPut path :: Request -> Path path req = T.unpack . T.intercalate (T.pack "/") $ T.pack "" : pathInfo req routeMiddleware :: Route -> Middleware routeMiddleware (Route method re) app req = case (requestMethod req == method, path req =~ re) of (True, Just routeApp) -> routeApp req _ -> app req -- | Turn the list of routes into `Middleware` waitraMiddleware :: [Route] -> Middleware waitraMiddleware = foldr ((.) . routeMiddleware) id jsonHeader :: H.Header jsonHeader = (H.hContentType, fromString "application/json") jsonApp :: (FromJSON a, ToJSON b) => (a -> IO (H.Status, H.ResponseHeaders, b)) -> Application jsonApp f req respond = do body <- strictRequestBody req case eitherDecode body of Left err -> respond $ responseLBS H.status400 [] $ fromString err Right x -> do (status, headers, y) <- f x respond $ responseLBS status (jsonHeader : headers) $ encode y jsonApp' :: ToJSON b => IO (H.Status, H.ResponseHeaders, b) -> Application jsonApp' io _req respond = do (status, headers, y) <- io respond $ responseLBS status (jsonHeader : headers) $ encode y