1 {-# LANGUAGE NoMonomorphismRestriction #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE InstanceSigs #-}
4 {-# OPTIONS -Wno-missing-signatures #-}
5 {-# OPTIONS -Wno-orphans #-}
6 module Hspec.Server.Router where
8 import Control.Monad (unless)
10 import Data.Char (Char)
11 import Data.Eq (Eq(..))
13 import Data.Maybe (Maybe(..))
14 import Data.Ord (Ord(..))
15 import Data.Proxy (Proxy(..))
16 import Data.String (String, IsString(..))
17 import System.IO (IO, putStrLn)
18 import Text.Show (Show(..), showString, showParen)
19 import qualified Control.Monad.Classes as MC
20 import qualified Data.List as List
21 import qualified Data.Map.Strict as Map
22 import qualified Data.Text.Lazy as TL
23 import qualified Data.Text.Lazy.Encoding as TL
24 import qualified Network.Wai.Handler.Warp as Warp
25 import qualified Test.Hspec as Hspec
26 import qualified Test.Hspec.Wai as Wai
29 import Symantic.HTTP.Server
32 import qualified Debug.Trace as Dbg
34 hspec = testSpecs $ describe "Router" $ do
36 Wai.with (return srv) $ do
38 it "call the right route" $ do
40 `Wai.shouldRespondWith` 200 { Wai.matchBody = mkBody $ fromString "0" }
41 it "call the right route" $ do
43 `Wai.shouldRespondWith` 200 { Wai.matchBody = mkBody $ fromString "3" }
45 describe "structure" $ do
46 it "distributes endpoints through static paths" $ do
47 inp_endpoint `shouldRouteAs` exp_endpoint
48 it "distributes nested routes through static paths" $ do
49 inp_static `shouldRouteAs` exp_static
50 it "distributes nested routes through dynamic paths" $ do
51 inp_dynamic `shouldRouteAs` exp_dynamic
52 it "properly reorders permuted static paths" $ do
53 inp_permute `shouldRouteAs` exp_permute
55 -- * Path tests Server
58 "a" </> "aa" </> get @String @'[PlainText]
60 "b" </> "bb" </> get @Int @'[PlainText]
62 "c" </> "cc" </> get @Int @'[PlainText]
64 "a" </> "AA" </> get @String @'[PlainText]
66 "b" </> "bb" </> get @Int @'[PlainText]
76 MC.exec $ putStrLn "/a/aa"
79 MC.exec $ putStrLn "/b/bb"
82 MC.exec $ putStrLn "/c/cc"
85 MC.exec $ putStrLn "/a/AA"
88 MC.exec $ putStrLn "/b/bb'"
92 warp = Warp.run 8080 srv
96 routerEq :: repr ~ Server => Router repr a b -> Router repr c d -> Bool
100 (Dbg.trace ("eq: x: " <> show x0) x0)
101 (Dbg.trace ("eq: y: " <> show y0) y0) in
102 Dbg.trace ("eq: r: " <> show r) r
106 go :: repr ~ Server => Router repr a b -> Router repr c d -> Bool
107 go (Router_Map xs) (Router_Map ys) =
108 List.and $ (\((kx,x),(ky,y)) -> kx==ky && routerEq x y) <$>
109 List.zip (Map.toList xs) (Map.toList ys)
110 go (Router_Seg x) (Router_Seg y) = x == y
111 go (Router_Cat xa xb) (Router_Cat ya yb) = routerEq xa ya && routerEq xb yb
112 go (Router_Alt xl xr) (Router_Alt yl yr) = routerEq xl yl && routerEq xr yr
113 go (Router_AltL x) y = routerEq x y
114 go (Router_AltR x) y = routerEq x y
115 go x (Router_AltL y) = routerEq x y
116 go x (Router_AltR y) = routerEq x y
117 go (Router_Cap xn) (Router_Cap yn) = xn == yn
118 go (Router_Caps xs) (Router_Caps ys) = go xs ys
120 go :: Captures xs -> Captures ys -> Bool
121 go (Captures0 xa xn) (Captures0 ya yn) = xn == xn
122 go (Captures2 xx xy) (Captures2 yx yy) = go xx yx && go xy yy
123 go Router_Any{} Router_Any{} = True
126 shouldRouteAs :: Router Server a b -> Router Server c d -> Hspec.Expectation
127 shouldRouteAs inp exp =
128 let inpR = router inp in
129 let expR = router exp in
130 unless (inpR`routerEq`expR) $
131 Hspec.expectationFailure $ "expected:\n" <> show expR <> "\nbut got:\n" <> show inpR
133 mkBody :: Wai.Body -> Wai.MatchBody
134 mkBody b = Wai.MatchBody $ \_ b' ->
137 else Just $ TL.unpack $
138 "expecting: "<>TL.decodeUtf8 b<>
139 " but got: "<>TL.decodeUtf8 b'<>"\n"
143 end = get @String @'[PlainText]
145 inp_endpoint = "a" </> end <!> "a" </> end
146 exp_endpoint = "a" </> (end <!> end)
148 inp_static = "a" </> "b" </> end <!> "a" </> "c" </> end
149 exp_static = "a" </> ("b" </> end <!> "c" </> end)
152 "a" </> capture @Int "foo" <.> "b" </> end
153 <!> "a" </> capture @Bool "bar" <.> "c" </> end
154 <!> "a" </> capture @Char "baz" <.> "d" </> end
156 "a" </> captures (Captures2 (Captures2 (Captures0 (Proxy @(Int -> Res)) "foo")
157 (Captures0 (Proxy @(Bool -> Res)) "bar"))
158 (Captures0 (Proxy @(Char -> Res)) "baz"))
159 <.> ("b" </> end <!> "c" </> end <!> "d" </> end)
160 type Res = ResponseArgs (Router Server) String '[PlainText]
163 "a" </> "b" </> "c" </> end
164 <!> "b" </> "a" </> "c" </> end
165 <!> "a" </> "c" </> "b" </> end
166 <!> "c" </> "a" </> "b" </> end
167 <!> "b" </> "c" </> "a" </> end
168 <!> "c" </> "b" </> "a" </> end
169 <!> "a" </> "a" </> "b" </> end
170 <!> "a" </> "a" </> "c" </> end
172 "a" </> ("b" </> "c" </> end
173 <!> "c" </> "b" </> end
174 <!> "a" </> "b" </> end)
175 <!> "b" </> ("a" </> "c" </> end
176 <!> "c" </> "a" </> end)
177 <!> "c" </> ("a" </> "b" </> end
178 <!> "b" </> "a" </> end)
179 <!> "a" </> "a" </> "c" </> end
183 ("a",L (L map [("a","b" <.> R X),("b","c" <.> L (L X)),("c","b" <.> L (R X))])
184 <!> "a" <.> "c" <.> X)
185 ,("b",L (L (R map [("a","c" <.> L X),("c","a" <.> R X)])))
186 ,("c",map [("a","b" <.> L (R (L X))),("b","a" <.> L (R (R X)))])
191 "a" </> "b" </> "c" </> end
192 <!> "b" </> "a" </> "c" </> end
193 <!> "a" </> "c" </> "b" </> end
194 <!> "c" </> "a" </> "b" </> end
195 <!> "b" </> "c" </> "a" </> end
196 <!> "c" </> "b" </> "a" </> end
198 api_PermuteRawEndRef = api_PermuteRef <!> api_Raw
199 api_PermuteRawBegin =
201 <!> "a" </> "b" </> "c" </> end
202 <!> "b" </> "a" </> "c" </> end
203 <!> "a" </> "c" </> "b" </> end
204 <!> "c" </> "a" </> "b" </> end
205 <!> "b" </> "c" </> "a" </> end
206 <!> "c" </> "b" </> "a" </> end
207 api_PermuteRawBeginRef = raw <!> api_PermuteRef