{-# LANGUAGE ExistentialQuantification , FlexibleContexts , FlexibleInstances , GeneralizedNewtypeDeriving , OverlappingInstances , MultiParamTypeClasses , TemplateHaskell , UndecidableInstances , UnicodeSyntax #-} module Network.HTTP.Lucu.Dispatcher.Internal ( HostMapper(..) , HostMap , ResourceMapper(..) , ResourceMap , ResourceTree , ResourceNode , greedy , nonGreedy , dispatch ) where import Control.Applicative hiding (empty) import Control.Monad.Trans.Maybe import Control.Monad.Unicode import Data.Collections import qualified Data.Map as M import Data.Monoid import Data.Monoid.Unicode import Network.HTTP.Lucu.Resource.Internal import Network.HTTP.Lucu.Utils import Network.URI hiding (path) import Prelude hiding (filter, lookup, null) import Prelude.Unicode -- |FIXME: docs -- -- Minimal complete definition: 'findResourceMap' class HostMapper α where findResourceMap ∷ Host → α → MaybeT IO ResourceMap hostMap ∷ α → HostMap {-# INLINE hostMap #-} hostMap = HMap -- |Container type for the 'HostMapper' type class. data HostMap = ∀α. HostMapper α ⇒ HMap α -- |FIXME: docs -- -- Minimal complete definition: 'findResource' class ResourceMapper α where findResource ∷ Path → α → MaybeT IO (Path, Resource) resourceMap ∷ α → ResourceMap {-# INLINE resourceMap #-} resourceMap = RMap -- |Container type for the 'ResourceMapper' type class. data ResourceMap = ∀α. ResourceMapper α ⇒ RMap α -- |'ResourceTree' is an opaque structure which is a map from resource -- path to 'Resource'. -- -- @ -- 'fromList' [ ([] , 'nonGreedy' '$' 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/stdio.h\" ) -- \/ -- , ([\"unistd\"], 'nonGreedy' '$' 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/unistd.h\") -- \/unistd -- ] -- @ -- -- Note that path segments are always represented as octet streams in -- this system. Lucu automatically decodes percent-encoded URIs but -- has no involvement in character encodings such as UTF-8, since RFC -- 2616 (HTTP/1.1) says nothing about character encodings to be used -- in \"http\" and \"https\" URI schemas. newtype ResourceTree = Tree (M.Map Path ResourceNode) deriving Monoid -- |FIXME: doc data ResourceNode = Greedy { nResource ∷ !Resource } | NonGreedy { nResource ∷ !Resource } -- |FIXME: doc greedy ∷ Resource → ResourceNode {-# INLINE CONLIKE greedy #-} greedy = Greedy -- |FIXME: doc nonGreedy ∷ Resource → ResourceNode {-# INLINE CONLIKE nonGreedy #-} nonGreedy = NonGreedy -- Instances of HostMapper ---------------------------------------------------- instance HostMapper HostMap where {-# INLINE findResourceMap #-} findResourceMap h (HMap α) = findResourceMap h α {-# INLINE hostMap #-} hostMap = id -- |'ResourceMap's are also 'HostMapper's too, which matches to any -- hosts. instance HostMapper ResourceMap where {-# INLINE findResourceMap #-} findResourceMap = const return -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next. instance HostMapper α ⇒ Unfoldable HostMap α where {-# INLINE insert #-} insert a (HMap b) = hostMap c where c ∷ Host → MaybeT IO ResourceMap {-# INLINEABLE c #-} c h = findResourceMap h a <|> findResourceMap h b {-# INLINE empty #-} empty = (∅) {-# INLINE singleton #-} singleton = hostMap -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next. instance Monoid HostMap where {-# INLINE mempty #-} mempty = hostMap e where e ∷ Host → MaybeT IO ResourceMap {-# INLINE e #-} e = const (fail (⊥)) {-# INLINE mappend #-} mappend = insert -- |Any 'Map's from 'Host' to 'ResourceMap' are also 'HostMapper's. instance Map α Host ResourceMap ⇒ HostMapper α where {-# INLINE findResourceMap #-} findResourceMap = (maybe (fail (⊥)) return ∘) ∘ lookup -- |An IO-based host mapper. instance HostMapper (Host → MaybeT IO ResourceMap) where {-# INLINE findResourceMap #-} findResourceMap = flip id -- |A pure host mapper. instance HostMapper (Host → Maybe ResourceMap) where {-# INLINE findResourceMap #-} findResourceMap = (maybe (fail (⊥)) return ∘) ∘ flip id -- Instances of ResourceMapper ------------------------------------------------ instance ResourceMapper ResourceMap where {-# INLINE findResource #-} findResource s (RMap α) = findResource s α {-# INLINE resourceMap #-} resourceMap = id -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next. instance ResourceMapper α ⇒ Unfoldable ResourceMap α where {-# INLINE insert #-} insert a (RMap b) = resourceMap c where c ∷ Path → MaybeT IO (Path, Resource) {-# INLINEABLE c #-} c s = findResource s a <|> findResource s b {-# INLINE empty #-} empty = (∅) {-# INLINE singleton #-} singleton = resourceMap -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next. instance Monoid ResourceMap where {-# INLINE mempty #-} mempty = resourceMap e where e ∷ Path → MaybeT IO (Path, Resource) {-# INLINE e #-} e = const (fail (⊥)) {-# INLINE mappend #-} mappend = insert -- |Any 'Map's from 'Path' to @('Path', 'Resource')@ are also -- 'ResourceMapper's. instance Map α Path (Path, Resource) ⇒ ResourceMapper α where {-# INLINE findResource #-} findResource = (maybe (fail (⊥)) return ∘) ∘ lookup -- |An IO-based resource mapper. instance ResourceMapper (Path → MaybeT IO (Path, Resource)) where {-# INLINE findResource #-} findResource = flip id -- |A pure resource mapper. instance ResourceMapper (Path → Maybe (Path, Resource)) where {-# INLINE findResource #-} findResource = (maybe (fail (⊥)) return ∘) ∘ flip id -- Instances of ResourceTree -------------------------------------------------- instance Unfoldable ResourceTree (Path, ResourceNode) where {-# INLINEABLE insert #-} insert (p, n) (Tree m) = Tree $ insert (canonPath p, n) m {-# INLINE empty #-} empty = Tree (∅) {-# INLINE singleton #-} singleton = Tree ∘ singleton canonPath ∷ (Collection c f, Foldable f e) ⇒ c → c {-# INLINEABLE canonPath #-} canonPath = filter ((¬) ∘ null) -- |'findResource' performs the longest prefix match on the tree, -- finding the most specific one. instance ResourceMapper ResourceTree where {-# INLINEABLE findResource #-} findResource p (Tree m) = case lookup p m of Just n → return (p, nResource n) Nothing → findGreedyResource p m findGreedyResource ∷ (Monad m, Map α Path ResourceNode) ⇒ Path → α → MaybeT m (Path, Resource) findGreedyResource p m = case back p of Nothing → fail (⊥) Just (p', _) → case lookup p' m of Just (Greedy r) → return (p', r) _ → findGreedyResource p' m -- dispatch ------------------------------------------------------------------- dispatch ∷ HostMapper α ⇒ URI → α → MaybeT IO (Path, Resource) dispatch uri = (findResource (uriPathSegments uri) =≪) ∘ findResourceMap (uriHost uri)