@@ -27,6 +27,7 @@ spec :: Spec
2727spec = describe " Servant.Server.Internal.Router" $ do
2828 routerSpec
2929 distributivitySpec
30+ serverLayoutSpec
3031
3132routerSpec :: Spec
3233routerSpec = do
@@ -101,12 +102,28 @@ distributivitySpec =
101102 it " properly handles mixing static paths at different levels" $ do
102103 level `shouldHaveSameStructureAs` levelRef
103104
105+ serverLayoutSpec :: Spec
106+ serverLayoutSpec =
107+ describe " serverLayout" $ do
108+ it " correctly represents the example API" $ do
109+ exampleLayout `shouldHaveLayout` expectedExampleLayout
110+ it " aggregates capture hints when different" $ do
111+ dynamic `shouldHaveLayout` expectedDynamicLayout
112+ it " nubs capture hints when equal" $ do
113+ dynamicSameType `shouldHaveLayout` expectedDynamicSameTypeLayout
114+
104115shouldHaveSameStructureAs ::
105116 (HasServer api1 '[] , HasServer api2 '[] ) => Proxy api1 -> Proxy api2 -> Expectation
106117shouldHaveSameStructureAs p1 p2 =
107118 unless (sameStructure (makeTrivialRouter p1) (makeTrivialRouter p2)) $
108119 expectationFailure (" expected:\n " ++ unpack (layout p2) ++ " \n but got:\n " ++ unpack (layout p1))
109120
121+ shouldHaveLayout ::
122+ (HasServer api '[] ) => Proxy api -> Text -> Expectation
123+ shouldHaveLayout p l =
124+ unless (routerLayout (makeTrivialRouter p) == l) $
125+ expectationFailure (" expected:\n " ++ unpack l ++ " \n but got:\n " ++ unpack (layout p))
126+
110127makeTrivialRouter :: (HasServer layout '[] ) => Proxy layout -> Router ()
111128makeTrivialRouter p =
112129 route p EmptyContext (emptyDelayed (FailFatal err501))
@@ -342,3 +359,74 @@ level = Proxy
342359
343360levelRef :: Proxy LevelRef
344361levelRef = Proxy
362+
363+ -- The example API for the 'layout' function.
364+ -- Should get factorized by the 'choice' smart constructor.
365+ type ExampleLayout =
366+ " a" :> " d" :> Get '[JSON ] NoContent
367+ :<|> " b" :> Capture " x" Int :> Get '[JSON ] Bool
368+ :<|> " c" :> Put '[JSON ] Bool
369+ :<|> " a" :> " e" :> Get '[JSON ] Int
370+ :<|> " b" :> Capture " x" Int :> Put '[JSON ] Bool
371+ :<|> Raw
372+
373+ exampleLayout :: Proxy ExampleLayout
374+ exampleLayout = Proxy
375+
376+ -- The expected representation of the example API layout
377+ --
378+ expectedExampleLayout :: Text
379+ expectedExampleLayout =
380+ " /\n \
381+ \├─ a/\n \
382+ \│ ├─ d/\n \
383+ \│ │ └─•\n \
384+ \│ └─ e/\n \
385+ \│ └─•\n \
386+ \├─ b/\n \
387+ \│ └─ <x::CaptureSingle>/\n \
388+ \│ ├─•\n \
389+ \│ ┆\n \
390+ \│ └─•\n \
391+ \├─ c/\n \
392+ \│ └─•\n \
393+ \┆\n \
394+ \└─ <raw>\n "
395+
396+ -- The expected representation of the Dynamic API layout.
397+ --
398+ expectedDynamicLayout :: Text
399+ expectedDynamicLayout =
400+ " /\n \
401+ \└─ a/\n \
402+ \ └─ <foo::CaptureSingle|bar::CaptureSingle|baz::CaptureSingle>/\n \
403+ \ ├─ b/\n \
404+ \ │ └─•\n \
405+ \ ├─ c/\n \
406+ \ │ └─•\n \
407+ \ └─ d/\n \
408+ \ └─•\n "
409+
410+ -- The same Dynamic API as above, except that the captured
411+ -- values have the same hints
412+ type DynamicSameType =
413+ " a" :> Capture " foo" Int :> " b" :> End
414+ :<|> " a" :> Capture " foo" Int :> " c" :> End
415+ :<|> " a" :> Capture " foo" Int :> " d" :> End
416+
417+ dynamicSameType :: Proxy DynamicSameType
418+ dynamicSameType = Proxy
419+
420+ -- The expected representation of the DynamicSameType API layout.
421+ --
422+ expectedDynamicSameTypeLayout :: Text
423+ expectedDynamicSameTypeLayout =
424+ " /\n \
425+ \└─ a/\n \
426+ \ └─ <foo::CaptureSingle>/\n \
427+ \ ├─ b/\n \
428+ \ │ └─•\n \
429+ \ ├─ c/\n \
430+ \ │ └─•\n \
431+ \ └─ d/\n \
432+ \ └─•\n "
0 commit comments