module Network.Loli.Engine where
import Control.Monad.State
import Data.Default
import Data.List (find)
import Hack
import Hack.Contrib.Middleware.Censor
import Hack.Contrib.Middleware.Config
import Hack.Contrib.Middleware.NotFound
import Hack.Contrib.Response
import Hack.Contrib.Utils hiding (get, put)
import MPS
import Network.Loli.Config
import Prelude hiding ((.), (/), (>), (^))
type RoutePath = (RequestMethod, String, AppUnit)
type EnvFilter = Env -> Env
type ResponseFilter = Response -> Response
type Param = (String, String)
data AppState = AppState
{
application :: Application
, env_filters :: [EnvFilter]
, response_filters :: [ResponseFilter]
, path :: String
}
instance Default AppState where
def = AppState def [id] [id] def
type AppUnit = State AppState ()
run_app :: String -> AppUnit -> Application
run_app path unit =
let state = execState unit def {path}
before = state.env_filters.map config
after = state.response_filters.map (to_io_filter > censor)
in
state.application.use (before ++ after)
where
to_io_filter f = \x -> return (f x)
router :: [RoutePath] -> Middleware
router h app' = \env'' ->
let path = env''.path_info
script = env''.script_name
mod_env location = env''
{ scriptName = script ++ location
, pathInfo = path.drop (location.length)
}
in
case h.find (match_route env'') of
Nothing -> app' env''
Just (_, location, app_state) ->
run_app location app_state (mod_env location)
where
match_route env' (method, path, _) =
env'.request_method.is method && env'.path_info.starts_with path
data Loli = Loli
{
routes :: [RoutePath]
, middlewares :: [Middleware]
, mimes :: [(String, String)]
}
instance Default Loli where
def = Loli def def def
type Unit = State Loli ()
loli :: Unit -> Application
loli unit = run unit (not_found empty_app)
where
run :: Unit -> Middleware
run unit' =
let s = execState unit' def
paths = s.routes
loli_app = router paths
mime_filter = lookup_mime (s.mimes)
stack = s.middlewares.use
pre = pre_installed_middlewares.use
in
use [pre, mime_filter, stack, loli_app]
set_application :: Application -> AppState -> AppState
set_application application x = x { application }
update :: (MonadState a m, Functor m) => (a -> a) -> m ()
update f = get ^ f >>= put
insert_last :: a -> [a] -> [a]
insert_last x xs = xs ++ [x]
add_route :: RoutePath -> Loli -> Loli
add_route r s = let xs = s.routes in s {routes = xs.insert_last r}
route :: RequestMethod -> String -> AppUnit -> Unit
route r s u = update $ add_route (r, s, u)
add_middleware :: Middleware -> Loli -> Loli
add_middleware x s =
let xs = s.middlewares in s {middlewares = xs.insert_last x}
add_mime :: String -> String -> Loli -> Loli
add_mime k v s = let xs = s.mimes in s {mimes = xs.insert_last (k, v)}
add_env_filter :: EnvFilter -> AppState -> AppState
add_env_filter x s =
let xs = s.env_filters in s {env_filters = xs.insert_last x}
add_response_filter :: ResponseFilter -> AppState -> AppState
add_response_filter x s =
let xs = s.response_filters in s {response_filters = xs.insert_last x}
request :: EnvFilter-> AppUnit
request x = add_env_filter x .update
response :: ResponseFilter -> AppUnit
response x = add_response_filter x .update
lookup_mime :: [(String, String)] -> Middleware
lookup_mime h app env = do
r <- app env
case h.only_fst.find mime >>= flip lookup h of
Nothing -> return r
Just v -> return $ r.set_content_type v
where mime x = env.path_info.ends_with ('.' : x)