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.String (String, IsString(..))
16 import System.IO (IO, putStrLn)
17 import Text.Show (Show(..), showString, showParen)
18 import qualified Control.Monad.Classes as MC
19 import qualified Data.List as List
20 import qualified Data.Map.Strict as Map
21 import qualified Data.Text.Lazy as TL
22 import qualified Data.Text.Lazy.Encoding as TL
23 import qualified Network.Wai.Handler.Warp as Warp
24 import qualified Test.Hspec as Hspec
25 import qualified Test.Hspec.Wai as Wai
28 import Symantic.HTTP.Server
31 hspec = testSpecs $ describe "Router" $ do
33 Wai.with (return srv) $ do
35 it "call the right route" $ do
37 `Wai.shouldRespondWith` 200 { Wai.matchBody = mkBody $ fromString "0" }
38 it "call the right route" $ do
40 `Wai.shouldRespondWith` 200 { Wai.matchBody = mkBody $ fromString "3" }
42 describe "structure" $ do
44 it "distributes endpoints through static paths" $ do
45 inp_endpoint `shouldRouteAs` exp_endpoint
46 it "distributes nested routes through static paths" $ do
47 inp_static `shouldRouteAs` exp_static
49 it "distributes nested routes through dynamic paths" $ do
50 inp_dynamic `shouldRouteAs` exp_dynamic
52 it "properly reorders permuted static paths" $ do
53 inp_permute `shouldRouteAs` exp_permute
56 -- * Path tests Server
59 "a" </> "aa" </> get @String @'[PlainText]
61 "b" </> "bb" </> get @Int @'[PlainText]
63 "c" </> "cc" </> get @Int @'[PlainText]
65 "a" </> "AA" </> get @String @'[PlainText]
67 "b" </> "bb" </> get @Int @'[PlainText]
77 MC.exec $ putStrLn "/a/aa"
80 MC.exec $ putStrLn "/b/bb"
83 MC.exec $ putStrLn "/c/cc"
86 MC.exec $ putStrLn "/a/AA"
89 MC.exec $ putStrLn "/b/bb'"
93 warp = Warp.run 8080 srv
97 routerEq :: Router repr a b -> Router repr c d -> Bool
98 routerEq (Router_Map xs) (Router_Map ys) =
99 List.and $ (\((kx,x),(ky,y)) -> kx==ky && routerEq x y) <$>
100 List.zip (Map.toList xs) (Map.toList ys)
101 routerEq (Router_Seg x) (Router_Seg y) = x == y
102 routerEq (Router_Cat xa xb) (Router_Cat ya yb) = routerEq xa ya && routerEq xb yb
103 routerEq (Router_Alt xl xr) (Router_Alt yl yr) = routerEq xl yl && routerEq xr yr
104 routerEq (Router_AltL x) y = routerEq x y
105 routerEq (Router_AltR x) y = routerEq x y
106 routerEq x (Router_AltL y) = routerEq x y
107 routerEq x (Router_AltR y) = routerEq x y
108 routerEq (Router_Caps xs) (Router_Caps ys) = go xs ys
111 Captures (Router repr) xs b ->
112 Captures (Router repr) ys d -> Bool
113 go (Captures0 xa xn xr) (Captures0 ya yn yr) =
114 xn == xn && routerEq xr yr
115 go (Captures2 xx xy) (Captures2 yx yy) =
117 routerEq Router_Any{} Router_Any{} = True
118 routerEq _x _y = False
120 shouldRouteAs :: Router Server a b -> Router Server c d -> Hspec.Expectation
121 shouldRouteAs inp exp =
122 let inpR = router inp in
123 let expR = router exp in
124 unless (inpR`routerEq`expR) $
125 Hspec.expectationFailure $ "expected:\n" <> show expR <> "\nbut got:\n" <> show inpR
127 mkBody :: Wai.Body -> Wai.MatchBody
128 mkBody b = Wai.MatchBody $ \_ b' ->
131 else Just $ TL.unpack $
132 "expecting: "<>TL.decodeUtf8 b<>
133 " but got: "<>TL.decodeUtf8 b'<>"\n"
137 end = get @String @'[PlainText]
139 inp_endpoint = "a" </> end <!> "a" </> end
140 exp_endpoint = "a" </> (end <!> end)
142 inp_static = "a" </> "b" </> end <!> "a" </> "c" </> end
143 exp_static = "a" </> ("b" </> end <!> "c" </> end)
146 "a" </> capture @Int "foo" <.> "b" </> end
147 <!> "a" </> capture @Bool "bar" <.> "c" </> end
148 <!> "a" </> capture @Char "baz" <.> "d" </> end
150 "a" </> capture @() "anything"
151 <.> ("b" </> end <!> "c" </> end <!> "d" </> end)
154 "a" </> "b" </> "c" </> end
155 <!> "b" </> "a" </> "c" </> end
156 <!> "a" </> "c" </> "b" </> end
157 <!> "c" </> "a" </> "b" </> end
158 <!> "b" </> "c" </> "a" </> end
159 <!> "c" </> "b" </> "a" </> end
160 <!> "a" </> "a" </> "b" </> end
162 "a" </> ("b" </> "c" </> end
163 <!> "c" </> "b" </> end
164 <!> "a" </> "b" </> end)
165 <!> "b" </> ("a" </> "c" </> end
166 <!> "c" </> "a" </> end)
167 <!> "c" </> ("a" </> "b" </> end
168 <!> "b" </> "a" </> end)
172 "a" </> "b" </> "c" </> end
173 <!> "b" </> "a" </> "c" </> end
174 <!> "a" </> "c" </> "b" </> end
175 <!> "c" </> "a" </> "b" </> end
176 <!> "b" </> "c" </> "a" </> end
177 <!> "c" </> "b" </> "a" </> end
179 api_PermuteRawEndRef = api_PermuteRef <!> api_Raw
180 api_PermuteRawBegin =
182 <!> "a" </> "b" </> "c" </> end
183 <!> "b" </> "a" </> "c" </> end
184 <!> "a" </> "c" </> "b" </> end
185 <!> "c" </> "a" </> "b" </> end
186 <!> "b" </> "c" </> "a" </> end
187 <!> "c" </> "b" </> "a" </> end
188 api_PermuteRawBeginRef = raw <!> api_PermuteRef