Skip to content

Commit

Permalink
Sessions Example complete; Added transformer layering
Browse files Browse the repository at this point in the history
  • Loading branch information
liamoc committed Jun 22, 2013
1 parent 17815b5 commit d415794
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 121 deletions.
2 changes: 1 addition & 1 deletion Geordi.hs
Expand Up @@ -16,5 +16,5 @@ import Geordi.TableBuilder.Wai
import Geordi.Response
import Network.Wai.Handler.Warp

geordi :: Int -> TableBuilder '[] '[] FilePath () -> IO ()
geordi :: Int -> TableBuilder (HandlerM FilePath) '[] '[] FilePath () -> IO ()
geordi p = run p . buildApplication tempFileBackend
60 changes: 41 additions & 19 deletions Geordi/Handler.hs
@@ -1,11 +1,12 @@
{-# LANGUAGE GADTs, KindSignatures, TypeOperators, DataKinds, GeneralizedNewtypeDeriving, NamedFieldPuns, RecordWildCards, OverloadedStrings #-}
{-# LANGUAGE GADTs, ScopedTypeVariables, UndecidableInstances, FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, KindSignatures, PolyKinds, FlexibleContexts, TypeOperators, DataKinds, GeneralizedNewtypeDeriving, NamedFieldPuns, RecordWildCards, OverloadedStrings, RankNTypes #-}
module Geordi.Handler ( -- * The Handler Type
Handler (..)
, HandlerStatus (..)
, MethodSingleton (..)
-- * Handler Monad
, HandlerM (..)
, runHandlerM
, MonadHandler (..)
-- * Control flow handler actions
, done
, continue
Expand All @@ -16,6 +17,8 @@ module Geordi.Handler ( -- * The Handler Type
, call
, link
, matchRequest
-- * Shifting monads in handlers
, monadSuffixH
) where

import Control.Arrow
Expand Down Expand Up @@ -43,12 +46,12 @@ newtype HandlerM f x = HandlerM (ReaderT (Request f) (WriterT (Endo Response) (R
, MonadReader (Request f)
, MonadResource
, MonadUnsafeIO
, MonadActive
, MonadActive
, MonadThrow
)


runHandlerM :: HandlerM f r -> Request f -> ResourceT IO (r , Response -> Response)
runHandlerM :: HandlerM f r -> Request f -> ResourceT IO (r , Response -> Response)
runHandlerM (HandlerM x) = fmap (second appEndo) . runWriterT . runReaderT x

data HandlerStatus = Continue
Expand All @@ -59,33 +62,52 @@ data MethodSingleton :: Method -> * where
MethodPost :: MethodSingleton POST
MethodAny :: MethodSingleton AnyMethod



data Handler :: * -> [SegmentType *] -> * where
Handler :: { method :: MethodSingleton m
, urlPat :: UrlPattern m f ts
, action :: (Types ts :--> HandlerM f HandlerStatus)
} -> Handler f ts

link :: Handler f ts -> Types (LinkSegments ts) :--> T.Text
class MonadHandler m f | m -> f where
liftHandler :: HandlerM f x -> m x
instance MonadHandler (HandlerM f) f where
liftHandler = id
instance (Monad m, MonadTrans t, MonadHandler m f) => MonadHandler (t m) f where
liftHandler = lift . liftHandler

data Handler :: (* -> *) -> * -> [SegmentType *] -> * where
Handler :: (MonadHandler m f) => { method :: MethodSingleton method
, urlPat :: UrlPattern method f ts
, action :: (Types ts :--> m HandlerStatus)
} -> Handler m f ts


monadSuffixH :: forall m2 m f n ts. (MonadHandler m2 f, MonadHandler m f)
=> (forall method. UrlPattern method f n)
-> (m HandlerStatus -> Types n :--> m2 HandlerStatus)
-> Handler m f ts
-> Handler m2 f (ts :++: n)
monadSuffixH p f (Handler {method, urlPat, action})
| Refl <- collectProof (undefined :: m2 HandlerStatus) (typesWitness urlPat) (typesWitness p)
, Refl <- typesProof p urlPat
= Handler { method = method
, urlPat = urlPat // p
, action = mapMany (typesWitness urlPat) f action
}

link :: Handler m f ts -> Types (LinkSegments ts) :--> T.Text
link (Handler {urlPat}) = linkUrl urlPat

call :: Handler f ts -> Types ts :--> HandlerM f HandlerStatus
call :: Handler m f ts -> Types ts :--> m HandlerStatus
call = action

done :: HandlerM f HandlerStatus
done :: Monad m => m HandlerStatus
done = return Done

continue :: HandlerM f HandlerStatus
continue :: Monad m => m HandlerStatus
continue = return Continue

set :: (Response -> Response) -> HandlerM f ()
set :: (MonadWriter (Endo f) m) => (f -> f) -> m ()
set = tell . Endo

respond :: (Response -> Response) -> HandlerM f HandlerStatus
respond e = set e >> done
respond :: (Monad m, MonadHandler m f) => (Response -> Response) -> m HandlerStatus
respond e = liftHandler (set e) >> done

matchRequest :: Handler f ts -> Request f -> Maybe (HandlerM f HandlerStatus)
matchRequest :: Handler m f ts -> Request f -> Maybe (m HandlerStatus)
matchRequest (Handler {..}) (Request {..}) = case method of
MethodAny -> matchUrl queries posts cookies files urlpieces urlPat action
x | methodString x == methodStr -> matchUrl queries posts cookies files urlpieces urlPat action
Expand Down
2 changes: 1 addition & 1 deletion Geordi/HandlerTable.hs
@@ -1,4 +1,4 @@
module Geordi.HandlerTable (HandlerTable, singleton, runTable) where
module Geordi.HandlerTable (HandlerTable, singleton, runTable, monadSuffixTable) where

import Geordi.HandlerTable.Internal

31 changes: 21 additions & 10 deletions Geordi/HandlerTable/Internal.hs
@@ -1,5 +1,5 @@
{-# LANGUAGE GADTs, KindSignatures, TypeOperators, DataKinds, PolyKinds, LambdaCase #-}
{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables, OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving, Rank2Types, ScopedTypeVariables, OverloadedStrings #-}
module Geordi.HandlerTable.Internal where
import qualified Data.Text.Lazy as T
import Data.Monoid
Expand All @@ -10,23 +10,34 @@ import Control.Monad.Trans.Resource
import Geordi.Util.Exists
import Geordi.Request
import Geordi.Response
import Geordi.UrlPattern

newtype HandlerTable f = HandlerTable [Exists (Handler f)] deriving (Monoid)
newtype HandlerTable m f = HandlerTable [Exists (Handler m f)] deriving (Monoid)

singleton :: Handler f hs -> HandlerTable f
monadSuffixTable :: (forall method. UrlPattern method f n)
-> (forall l. Handler m f l -> Handler m2 f (l :++: n))
-> HandlerTable m f -> HandlerTable m2 f
monadSuffixTable i f (HandlerTable t) = HandlerTable $ map (existsSuffix i f) t
where
existsSuffix :: (forall method. UrlPattern method f n)
-> (forall l. Handler m f l -> Handler m2 f (l :++: n))
-> Exists (Handler m f) -> Exists (Handler m2 f)
existsSuffix _ f (ExI x) = (ExI $ f x)

singleton :: Handler m f hs -> HandlerTable m f
singleton = HandlerTable . (:[]) . ExI

runTable :: HandlerTable f -> Request f -> ResourceT IO Response
runTable :: HandlerTable (HandlerM f) f -> Request f -> ResourceT IO Response
runTable (HandlerTable x) = go initialResponse x
where go :: Response -> [Exists (Handler f)] -> Request f -> ResourceT IO Response
where go :: Response -> [Exists (Handler (HandlerM f) f)] -> Request f -> ResourceT IO Response
go res [] _ = return $ res
go res (ExI h : hs ) req
go res (ExI h : hs ) req
= case matchRequest h req of
Nothing -> go res hs req
Just act -> second ($ res) <$> runHandlerM act req >>= \case
Just act -> second ($ res) <$> runHandlerM act req >>= \case
(Continue , res') -> go res' hs req
(Done , res') -> return res'
initialResponse = text (
(Done , res') -> return res'
initialResponse = text (
"Captain Picard to the bridge!\n Captain, we've got a problem with the warp core, or the phase inducers --- or some other damn thing.\n\n"
<> " ______ \n"
<> " ___.--------'------`---------.____ \n"
Expand All @@ -41,4 +52,4 @@ runTable (HandlerTable x) = go initialResponse x
<> " \\________|_.-' \n"
<> " \n"
<> " (Most likely, the developer hasn't added a `catch-all' 404 handler... yet) \n"
) emptyResponse
) emptyResponse
3 changes: 2 additions & 1 deletion Geordi/HandlerTable/Wai.hs
Expand Up @@ -5,6 +5,7 @@ import Geordi.HandlerTable
import Geordi.Request.Wai
import Geordi.Response.Wai
import Geordi.FileBackend
import Geordi.Handler

asApplication :: FileBackend f -> HandlerTable f -> W.Application
asApplication :: FileBackend f -> HandlerTable (HandlerM f) f -> W.Application
asApplication backend table req = fromWai req backend >>= runTable table >>= return . toWai
7 changes: 4 additions & 3 deletions Geordi/TableBuilder.hs
@@ -1,9 +1,10 @@
module Geordi.TableBuilder ( -- * The 'TableBuilder' Type
TableBuilder
TableBuilder
-- * Prefixes and Suffixes
, prefix
, suffix
-- * Adding handlers
, monadSuffix
-- * Adding handlers
, add
, handle
, get
Expand All @@ -13,4 +14,4 @@ module Geordi.TableBuilder ( -- * The 'TableBuilder' Type
, buildTable
) where

import Geordi.TableBuilder.Internal
import Geordi.TableBuilder.Internal
74 changes: 45 additions & 29 deletions Geordi/TableBuilder/Internal.hs
@@ -1,77 +1,93 @@
{-# LANGUAGE GeneralizedNewtypeDeriving, DataKinds, GADTs, RankNTypes, KindSignatures, TypeOperators, OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving, DataKinds, GADTs, RankNTypes, KindSignatures, TypeOperators, OverloadedStrings,ScopedTypeVariables #-}
module Geordi.TableBuilder.Internal where

import Geordi.HandlerTable
import Geordi.UrlPattern
import Geordi.Handler

import Control.Arrow
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Applicative

data GenUrlPattern (f :: * ) (x :: [SegmentType *]) where
data GenUrlPattern (f :: * ) (x :: [SegmentType *]) where
Generalise :: (forall m. UrlPattern m f x) -> GenUrlPattern f x

interpret :: GenUrlPattern f x -> UrlPattern m f x
interpret (Generalise x) = x

newtype TableBuilder p s f x = TableBuilder (ReaderT (GenUrlPattern f p, GenUrlPattern f s) (Writer (HandlerTable f)) x)
newtype TableBuilder m p s f x = TableBuilder (ReaderT (GenUrlPattern f p, GenUrlPattern f s) (Writer (HandlerTable m f)) x)
deriving ( Functor
, Monad
, Applicative
, MonadWriter (HandlerTable f)
, MonadWriter (HandlerTable m f)
, MonadFix
)

prefix :: (forall m. UrlPattern m f n) -> TableBuilder (p :++: n) s f x -> TableBuilder p s f x
prefix p (TableBuilder x)
prefix :: (forall method. UrlPattern method f n) -> TableBuilder m (p :++: n) s f x -> TableBuilder m p s f x
prefix p (TableBuilder x)
= do (Generalise v, s) <- TableBuilder ask
let (r,w) = runWriter $ flip runReaderT (Generalise (v // p), s) x
tell w
return r

suffix :: (forall m. UrlPattern m f n) -> TableBuilder p (s :++: n) f x -> TableBuilder p s f x
suffix p (TableBuilder x)
suffix :: (forall method. UrlPattern method f n) -> TableBuilder m p (s :++: n) f x -> TableBuilder m p s f x
suffix p (TableBuilder x)
= do (px, Generalise v) <- TableBuilder ask
let (r, w) = runWriter $ flip runReaderT (px, Generalise (v // p)) x
tell w
return r

add :: Handler f u -> TableBuilder p s f ()
add :: Handler m f u -> TableBuilder m p s f ()
add = tell . singleton

monadSuffix :: forall m2 m f n p s x. (MonadHandler m2 f, MonadHandler m f)
=> (forall method. UrlPattern method f n)
-> (m HandlerStatus -> Types n :--> m2 HandlerStatus)
-> TableBuilder m p s f x
-> TableBuilder m2 p s f x
monadSuffix p ml (TableBuilder rtw) = let f :: Handler m f ts -> Handler m2 f (ts :++: n)
f = monadSuffixH p ml
g :: HandlerTable m f -> HandlerTable m2 f
g = monadSuffixTable p f
in TableBuilder $ do x <- ask
lift $ mapWriter (second g) (runReaderT rtw x)
-- | Adds a handler to the table and returns it
handle :: MethodSingleton m -- ^ 'MethodGet' or 'MethodPost', indicating the HTTP method this handler will require
-> UrlPattern m f ts -- ^ A 'UrlPattern' for this handler to check requests against
-> (Types ((prefix :++: ts) :++: suffix) :--> HandlerM f HandlerStatus) -- ^ An action to execute if the pattern matches
-> TableBuilder prefix suffix f (Handler f ((prefix :++: ts) :++: suffix)) -- ^ Action that returns the handler and adds it to the table
handle :: (MonadHandler m f)
=> MethodSingleton method -- ^ 'MethodGet' or 'MethodPost', indicating the HTTP method this handler will require
-> UrlPattern method f ts -- ^ A 'UrlPattern' for this handler to check requests against
-> (Types ((prefix :++: ts) :++: suffix) :--> m HandlerStatus) -- ^ An action to execute if the pattern matches
-> TableBuilder m prefix suffix f (Handler m f ((prefix :++: ts) :++: suffix)) -- ^ Action that returns the handler and adds it to the table
handle m p a
= do (px, sx) <- (interpret *** interpret)
<$> (TableBuilder ask :: TableBuilder prefix suffix f (GenUrlPattern f prefix, GenUrlPattern f suffix))
let pattern = ((px // p) // sx)
= do (px, sx) <- (interpret *** interpret)
<$> (TableBuilder ask :: TableBuilder m prefix suffix f (GenUrlPattern f prefix, GenUrlPattern f suffix))
let pattern = ((px // p) // sx)
let x = Handler m pattern a
add x
return x


-- | Simply 'handle' 'MethodGet'
get :: UrlPattern GET f ts -- ^ Url pattern
-> (Types ((prefix :++: ts) :++: suffix) :--> HandlerM f HandlerStatus) -- ^ Handler action
-> TableBuilder prefix suffix f (Handler f ((prefix :++: ts) :++: suffix))
get :: (MonadHandler m f)
=> UrlPattern GET f ts -- ^ Url pattern
-> (Types ((prefix :++: ts) :++: suffix) :--> m HandlerStatus) -- ^ Handler action
-> TableBuilder m prefix suffix f (Handler m f ((prefix :++: ts) :++: suffix))
get = handle MethodGet
-- | Simply 'handle' 'MethodPost'
post :: UrlPattern POST f ts -- ^ Url pattern
-> (Types ((prefix :++: ts) :++: suffix) :--> HandlerM f HandlerStatus) -- ^ Handler action
-> TableBuilder prefix suffix f (Handler f ((prefix :++: ts) :++: suffix))
post :: (MonadHandler m f)
=> UrlPattern POST f ts -- ^ Url pattern
-> (Types ((prefix :++: ts) :++: suffix) :--> m HandlerStatus) -- ^ Handler action
-> TableBuilder m prefix suffix f (Handler m f ((prefix :++: ts) :++: suffix))
post = handle MethodPost
-- | Simply 'handle' 'MethodAny'
request :: UrlPattern AnyMethod f ts -- ^ Url pattern
-> (Types ((prefix :++: ts) :++: suffix) :--> HandlerM f HandlerStatus) -- ^ Handler action
-> TableBuilder prefix suffix f (Handler f ((prefix :++: ts) :++: suffix))
request :: (MonadHandler m f)
=> UrlPattern AnyMethod f ts -- ^ Url pattern
-> (Types ((prefix :++: ts) :++: suffix) :--> m HandlerStatus) -- ^ Handler action
-> TableBuilder m prefix suffix f (Handler m f ((prefix :++: ts) :++: suffix))
request = handle MethodAny

buildTable :: TableBuilder '[] '[] f r -> (r, HandlerTable f)
buildTable :: TableBuilder m '[] '[] f r -> (r, HandlerTable m f)
buildTable (TableBuilder x) = runWriter
. flip runReaderT (Generalise nil , Generalise nil )
. flip runReaderT (Generalise nil , Generalise nil )
$ x

3 changes: 2 additions & 1 deletion Geordi/TableBuilder/Wai.hs
Expand Up @@ -6,6 +6,7 @@ import qualified Network.Wai as W
import Geordi.FileBackend
import Geordi.TableBuilder
import Geordi.HandlerTable.Wai
import Geordi.Handler

buildApplication :: FileBackend f -> TableBuilder '[] '[] f () -> W.Application
buildApplication :: FileBackend f -> TableBuilder (HandlerM f) '[] '[] f () -> W.Application
buildApplication b = asApplication b . snd . buildTable
5 changes: 5 additions & 0 deletions Geordi/UrlPattern.hs
Expand Up @@ -29,6 +29,11 @@ module Geordi.UrlPattern ( -- * The 'UrlPattern' monoid
-- * Matching and linking
, linkUrl
, matchUrl
-- * Some useful type-level proofs
, typesProof
, collectProof
, linkWitness
, typesWitness
) where

import Geordi.UrlPattern.Internal
Expand Down

0 comments on commit d415794

Please sign in to comment.