2 ExistentialQuantification
5 , GeneralizedNewtypeDeriving
7 , MultiParamTypeClasses
12 module Network.HTTP.Lucu.Dispatcher.Internal
25 import Control.Applicative hiding (empty)
26 import Control.Monad.Trans.Maybe
27 import Control.Monad.Unicode
28 import Data.Collections
29 import qualified Data.Map as M
31 import Data.Monoid.Unicode
32 import Network.HTTP.Lucu.Resource.Internal
33 import Network.HTTP.Lucu.Utils
34 import Network.URI hiding (path)
35 import Prelude hiding (filter, lookup, null)
36 import Prelude.Unicode
40 -- Minimal complete definition: 'findResourceMap'
41 class HostMapper α where
42 findResourceMap ∷ Host → α → MaybeT IO ResourceMap
44 {-# INLINE hostMap #-}
47 -- |Container type for the 'HostMapper' type class.
48 data HostMap = ∀α. HostMapper α ⇒ HMap α
52 -- Minimal complete definition: 'findResource'
53 class ResourceMapper α where
54 findResource ∷ Path → α → MaybeT IO (Path, Resource)
55 resourceMap ∷ α → ResourceMap
56 {-# INLINE resourceMap #-}
59 -- |Container type for the 'ResourceMapper' type class.
60 data ResourceMap = ∀α. ResourceMapper α ⇒ RMap α
62 -- |'ResourceTree' is an opaque structure which is a map from resource
63 -- path to 'Resource'.
66 -- 'fromList' [ ([] , 'nonGreedy' '$' 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/stdio.h\" ) -- \/
67 -- , ([\"unistd\"], 'nonGreedy' '$' 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/unistd.h\") -- \/unistd
71 -- Note that path segments are always represented as octet streams in
72 -- this system. Lucu automatically decodes percent-encoded URIs but
73 -- has no involvement in character encodings such as UTF-8, since RFC
74 -- 2616 (HTTP/1.1) says nothing about character encodings to be used
75 -- in \"http\" and \"https\" URI schemas.
76 newtype ResourceTree = Tree (M.Map Path ResourceNode)
81 = Greedy { nResource ∷ !Resource }
82 | NonGreedy { nResource ∷ !Resource }
85 greedy ∷ Resource → ResourceNode
86 {-# INLINE CONLIKE greedy #-}
90 nonGreedy ∷ Resource → ResourceNode
91 {-# INLINE CONLIKE nonGreedy #-}
95 -- Instances of HostMapper ----------------------------------------------------
96 instance HostMapper HostMap where
97 {-# INLINE findResourceMap #-}
98 findResourceMap h (HMap α) = findResourceMap h α
99 {-# INLINE hostMap #-}
102 -- |'ResourceMap's are also 'HostMapper's too, which matches to any
104 instance HostMapper ResourceMap where
105 {-# INLINE findResourceMap #-}
106 findResourceMap = const return
108 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
109 instance HostMapper α ⇒ Unfoldable HostMap α where
110 {-# INLINE insert #-}
111 insert a (HMap b) = hostMap c
113 c ∷ Host → MaybeT IO ResourceMap
115 c h = findResourceMap h a <|> findResourceMap h b
118 {-# INLINE singleton #-}
121 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
122 instance Monoid HostMap where
123 {-# INLINE mempty #-}
126 e ∷ Host → MaybeT IO ResourceMap
129 {-# INLINE mappend #-}
132 -- |Any 'Map's from 'Host' to 'ResourceMap' are also 'HostMapper's.
133 instance Map α Host ResourceMap ⇒ HostMapper α where
134 {-# INLINE findResourceMap #-}
135 findResourceMap = (maybe (fail (⊥)) return ∘) ∘ lookup
137 -- |An IO-based host mapper.
138 instance HostMapper (Host → MaybeT IO ResourceMap) where
139 {-# INLINE findResourceMap #-}
140 findResourceMap = flip id
142 -- |A pure host mapper.
143 instance HostMapper (Host → Maybe ResourceMap) where
144 {-# INLINE findResourceMap #-}
145 findResourceMap = (maybe (fail (⊥)) return ∘) ∘ flip id
147 -- Instances of ResourceMapper ------------------------------------------------
148 instance ResourceMapper ResourceMap where
149 {-# INLINE findResource #-}
150 findResource s (RMap α) = findResource s α
151 {-# INLINE resourceMap #-}
154 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
155 instance ResourceMapper α ⇒ Unfoldable ResourceMap α where
156 {-# INLINE insert #-}
157 insert a (RMap b) = resourceMap c
159 c ∷ Path → MaybeT IO (Path, Resource)
161 c s = findResource s a <|> findResource s b
164 {-# INLINE singleton #-}
165 singleton = resourceMap
167 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
168 instance Monoid ResourceMap where
169 {-# INLINE mempty #-}
170 mempty = resourceMap e
172 e ∷ Path → MaybeT IO (Path, Resource)
175 {-# INLINE mappend #-}
178 -- |Any 'Map's from 'Path' to @('Path', 'Resource')@ are also
179 -- 'ResourceMapper's.
180 instance Map α Path (Path, Resource) ⇒ ResourceMapper α where
181 {-# INLINE findResource #-}
182 findResource = (maybe (fail (⊥)) return ∘) ∘ lookup
184 -- |An IO-based resource mapper.
185 instance ResourceMapper (Path → MaybeT IO (Path, Resource)) where
186 {-# INLINE findResource #-}
187 findResource = flip id
189 -- |A pure resource mapper.
190 instance ResourceMapper (Path → Maybe (Path, Resource)) where
191 {-# INLINE findResource #-}
192 findResource = (maybe (fail (⊥)) return ∘) ∘ flip id
194 -- Instances of ResourceTree --------------------------------------------------
195 instance Unfoldable ResourceTree (Path, ResourceNode) where
196 {-# INLINEABLE insert #-}
197 insert (p, n) (Tree m) = Tree $ insert (canonPath p, n) m
200 {-# INLINE singleton #-}
201 singleton = Tree ∘ singleton
203 canonPath ∷ (Collection c f, Foldable f e) ⇒ c → c
204 {-# INLINEABLE canonPath #-}
205 canonPath = filter ((¬) ∘ null)
207 -- |'findResource' performs the longest prefix match on the tree,
208 -- finding the most specific one.
209 instance ResourceMapper ResourceTree where
210 {-# INLINEABLE findResource #-}
211 findResource p (Tree m)
213 Just n → return (p, nResource n)
214 Nothing → findGreedyResource p m
216 findGreedyResource ∷ (Monad m, Map α Path ResourceNode)
219 → MaybeT m (Path, Resource)
220 findGreedyResource p m
223 Just (p', _) → case lookup p' m of
226 _ → findGreedyResource p' m
228 -- dispatch -------------------------------------------------------------------
229 dispatch ∷ HostMapper α ⇒ URI → α → MaybeT IO (Path, Resource)
231 = (findResource (uriPathSegments uri) =≪)
232 ∘ findResourceMap (uriHost uri)