3 , ExistentialQuantification
6 , 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: 'findHostMap'
41 class SchemeMapper α where
42 findHostMap ∷ Scheme → α → MaybeT IO HostMap
43 schemeMap ∷ α → SchemeMap
44 {-# INLINE schemeMap #-}
47 -- |Container type for the 'SchemeMapper' type class.
48 data SchemeMap = ∀α. SchemeMapper α ⇒ SMap α
52 -- Minimal complete definition: 'findResourceMap'
53 class HostMapper α where
54 findResourceMap ∷ Host → α → MaybeT IO ResourceMap
56 {-# INLINE hostMap #-}
59 -- |Container type for the 'HostMapper' type class.
60 data HostMap = ∀α. HostMapper α ⇒ HMap α
64 -- Minimal complete definition: 'findResource'
65 class ResourceMapper α where
66 findResource ∷ PathSegments → α → MaybeT IO (PathSegments, Resource)
67 resourceMap ∷ α → ResourceMap
68 {-# INLINE resourceMap #-}
71 -- |Container type for the 'ResourceMapper' type class.
72 data ResourceMap = ∀α. ResourceMapper α ⇒ RMap α
75 newtype ResourceTree = Root ResourceNode
80 | NonGreedy !Resource !SubTree
84 = M.Map PathSegment ResourceNode
86 -- Instances of SchemeMapper --------------------------------------------------
87 instance SchemeMapper SchemeMap where
88 {-# INLINE findHostMap #-}
89 findHostMap s (SMap α) = findHostMap s α
90 {-# INLINE schemeMap #-}
93 -- |'HostMap's are also 'SchemeMapper's too, which matches to any
95 instance SchemeMapper HostMap where
96 {-# INLINE findHostMap #-}
97 findHostMap = const return
99 -- |'ResourceMap's are also 'SchemeMapper's too, which matches to any
100 -- schemes and hosts.
101 instance SchemeMapper ResourceMap where
102 {-# INLINE findHostMap #-}
103 findHostMap _ r = return $ hostMap f
105 f ∷ Host → Maybe ResourceMap
109 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
110 instance SchemeMapper α ⇒ Unfoldable SchemeMap α where
111 {-# INLINE insert #-}
112 insert a (SMap b) = schemeMap c
114 c ∷ Scheme → MaybeT IO HostMap
116 c s = findHostMap s a <|> findHostMap s b
119 {-# INLINE singleton #-}
120 singleton = schemeMap
122 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
123 instance Monoid SchemeMap where
124 {-# INLINE mempty #-}
127 e ∷ Scheme → MaybeT IO HostMap
130 {-# INLINE mappend #-}
133 instance Map α Scheme HostMap ⇒ SchemeMapper α where
134 {-# INLINE findHostMap #-}
135 findHostMap = (maybe (fail (⊥)) return ∘) ∘ lookup
137 -- |An IO-based scheme mapper.
138 instance SchemeMapper (Scheme → MaybeT IO HostMap) where
139 {-# INLINE findHostMap #-}
140 findHostMap = flip id
142 -- |A pure scheme mapper.
143 instance SchemeMapper (Scheme → Maybe HostMap) where
144 {-# INLINE findHostMap #-}
145 findHostMap = (maybe (fail (⊥)) return ∘) ∘ flip id
148 -- Instances of HostMapper ----------------------------------------------------
149 instance HostMapper HostMap where
150 {-# INLINE findResourceMap #-}
151 findResourceMap h (HMap α) = findResourceMap h α
152 {-# INLINE hostMap #-}
155 -- |'ResourceMap's are also 'HostMapper's too, which matches to any
157 instance HostMapper ResourceMap where
158 {-# INLINE findResourceMap #-}
159 findResourceMap = const return
161 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
162 instance HostMapper α ⇒ Unfoldable HostMap α where
163 {-# INLINE insert #-}
164 insert a (HMap b) = hostMap c
166 c ∷ Host → MaybeT IO ResourceMap
168 c h = findResourceMap h a <|> findResourceMap h b
171 {-# INLINE singleton #-}
174 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
175 instance Monoid HostMap where
176 {-# INLINE mempty #-}
179 e ∷ Host → MaybeT IO ResourceMap
182 {-# INLINE mappend #-}
185 instance Map α Host ResourceMap ⇒ HostMapper α where
186 {-# INLINE findResourceMap #-}
187 findResourceMap = (maybe (fail (⊥)) return ∘) ∘ lookup
189 -- |An IO-based host mapper.
190 instance HostMapper (Host → MaybeT IO ResourceMap) where
191 {-# INLINE findResourceMap #-}
192 findResourceMap = flip id
194 -- |A pure host mapper.
195 instance HostMapper (Host → Maybe ResourceMap) where
196 {-# INLINE findResourceMap #-}
197 findResourceMap = (maybe (fail (⊥)) return ∘) ∘ flip id
199 -- Instances of ResourceMapper ------------------------------------------------
200 instance ResourceMapper ResourceMap where
201 {-# INLINE findResource #-}
202 findResource s (RMap α) = findResource s α
203 {-# INLINE resourceMap #-}
206 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
207 instance ResourceMapper α ⇒ Unfoldable ResourceMap α where
208 {-# INLINE insert #-}
209 insert a (RMap b) = resourceMap c
211 c ∷ PathSegments → MaybeT IO (PathSegments, Resource)
213 c s = findResource s a <|> findResource s b
216 {-# INLINE singleton #-}
217 singleton = resourceMap
219 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
220 instance Monoid ResourceMap where
221 {-# INLINE mempty #-}
222 mempty = resourceMap e
224 e ∷ PathSegments → MaybeT IO (PathSegments, Resource)
227 {-# INLINE mappend #-}
230 instance Map α PathSegments (PathSegments, Resource) ⇒ ResourceMapper α where
231 {-# INLINE findResource #-}
232 findResource = (maybe (fail (⊥)) return ∘) ∘ lookup
234 -- |An IO-based resource mapper.
235 instance ResourceMapper (PathSegments → MaybeT IO (PathSegments, Resource)) where
236 {-# INLINE findResource #-}
237 findResource = flip id
239 -- |A pure resource mapper.
240 instance ResourceMapper (PathSegments → Maybe (PathSegments, Resource)) where
241 {-# INLINE findResource #-}
242 findResource = (maybe (fail (⊥)) return ∘) ∘ flip id
244 -- Instances of ResourceTree --------------------------------------------------
245 instance Unfoldable ResourceTree (PathSegments, ResourceNode) where
246 {-# INLINE insert #-}
247 insert e (Root root) = Root $ insert e root
251 instance Monoid ResourceTree where
252 {-# INLINE mempty #-}
254 {-# INLINE mappend #-}
255 mappend (Root a) (Root b)
258 -- Instances of ResourceNode --------------------------------------------------
259 instance Unfoldable ResourceNode (PathSegments, ResourceNode) where
260 {-# INLINEABLE insert #-}
261 insert (p, n) = insertNodeAt (canonPath p) n
265 canonPath ∷ (Collection c f, Foldable f e) ⇒ c → c
266 {-# INLINEABLE canonPath #-}
267 canonPath = filter ((¬) ∘ null)
269 insertNodeAt ∷ PathSegments → ResourceNode → ResourceNode → ResourceNode
270 {-# INLINEABLE insertNodeAt #-}
275 | null xs → Branch (singleton (x, a)) ⊕ b
276 | otherwise → insertNodeAt xs a (∅) ⊕ b
278 instance Monoid ResourceNode where
279 {-# INLINE mempty #-}
281 {-# INLINEABLE mappend #-}
282 mappend _ (Greedy r ) = Greedy r
283 mappend (Greedy _ ) (NonGreedy r n) = NonGreedy r n
284 mappend (NonGreedy _ m) (NonGreedy r n) = NonGreedy r (m ⊕ n)
285 mappend (Branch m) (NonGreedy r n) = NonGreedy r (m ⊕ n)
286 mappend (Greedy r ) (Branch _) = Greedy r
287 mappend (NonGreedy r m) (Branch n) = NonGreedy r (m ⊕ n)
288 mappend (Branch m) (Branch n) = Branch (m ⊕ n)
290 -- dispatch -------------------------------------------------------------------
291 dispatch ∷ SchemeMapper α ⇒ URI → α → MaybeT IO (PathSegments, Resource)
293 = (findResource (uriPathSegments uri) =≪)
294 ∘ (findResourceMap (uriHost uri) =≪)
295 ∘ findHostMap (uriCIScheme uri)
298 -- |'ResTree' is an opaque structure which is a map from resource path
300 newtype ResTree = ResTree ResNode -- root だから Map ではない
301 type ResSubtree = Map ByteString ResNode
302 data ResNode = ResNode (Maybe Resource) ResSubtree
304 -- |'mkResTree' converts a list of @(path, def)@ to a 'ResTree' e.g.
307 -- mkResTree [ ([] , 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/stdio.h\" ) -- \/
308 -- , ([\"unistd\"], 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/unistd.h\") -- \/unistd
312 -- Note that path components are always represented as octet streams
313 -- in this system. Lucu automatically decodes percent-encoded URIs but
314 -- has no involvement in character encodings such as UTF-8, since RFC
315 -- 2616 (HTTP/1.1) says nothing about character encodings to be used
316 -- in \"http\" and \"https\" URI schemas.
317 mkResTree ∷ [ ([ByteString], Resource) ] → ResTree
318 mkResTree = processRoot ∘ map (first canonicalisePath)
320 canonicalisePath ∷ [ByteString] → [ByteString]
321 canonicalisePath = filter ((¬) ∘ BS.null)
323 processRoot ∷ [ ([ByteString], Resource) ] → ResTree
325 = let (roots, nonRoots) = partition (\(path, _) → null path) list
326 children = processNonRoot nonRoots
329 -- The root has no resources. Maybe there's one at
330 -- somewhere like "/foo".
331 ResTree (ResNode Nothing children)
333 -- There is a root resource.
334 let (_, def) = last roots
336 ResTree (ResNode (Just def) children)
338 processNonRoot ∷ [ ([ByteString], Resource) ] → ResSubtree
340 = let subtree = M.fromList [(name, node name)
342 childNames = [name | (name:_, _) ← list]
343 node name = let defs = [def | (path, def) ← list, path ≡ [name]]
346 -- No resources are defined
347 -- here. Maybe there's one at
348 -- somewhere below this node.
349 ResNode Nothing children
351 -- There is a resource here.
352 ResNode (Just $ last defs) children
353 children = processNonRoot [(path, def)
354 | (_:path, def) ← list]