packages feed

yesod-core-1.7.0.0: test/Route/DeepAritySpec.hs

{-# LANGUAGE TemplateHaskell #-}

-- | Exercises the reify/'typeArity' path that 'assertNestedSubArity' runs
-- inside 'mkNestedDispatchInstanceWith'\'s recursion — the part the pure
-- 'checkNestedSubArity' unit tests in "Route.SubDispatchAritySpec" can't reach.
-- A 2nd-level-or-deeper nested datatype whose arity doesn't match the site's
-- type-argument count must be caught by the guard's @fail@ (an actionable
-- 'ArityMismatch') rather than slip through to a cryptic kind error in
-- generated code.
module Route.DeepAritySpec (spec) where

import Test.Hspec
import Language.Haskell.TH (recover)
import Yesod.Routes.TH.Internal
    ( assertNestedSubArity
    , resolveRouteCon
    , typeArity
    , ArityCallSite(..)
    , SubsiteName(..)
    , SubsiteArity(..)
    )
import Route.DeepArityTypes (ParamSite, MatchingDeepR, WrongDeepR)

-- | The reified arities feeding the guard, extracted at compile time through
-- the same 'typeArity' call 'assertNestedSubArity' makes.
reifiedArities :: (Int, Int, Int)
reifiedArities =
    $(do
        a <- typeArity ''ParamSite
        b <- typeArity ''MatchingDeepR
        c <- typeArity ''WrongDeepR
        [| (a, b, c) |])

-- | 'True' iff the arity guard fired its @fail@ for a (site arity 1) vs.
-- (@WrongDeepR@ arity 0) pairing — i.e. the deep mismatch was caught at the
-- guard rather than escaping into ill-kinded generated code.
wrongArityCaught :: Bool
wrongArityCaught =
    $(do
        rc <- resolveRouteCon "WrongDeepR"
        recover [| True |] $ do
            assertNestedSubArity TopLevelCall (SubsiteName "ParamSite") (SubsiteArity 1) rc
            [| False |])

-- | 'True' iff a matching (arity 1 vs. 1) pairing passes the guard cleanly.
matchingArityOk :: Bool
matchingArityOk =
    $(do
        rc <- resolveRouteCon "MatchingDeepR"
        recover [| False |] $ do
            assertNestedSubArity TopLevelCall (SubsiteName "ParamSite") (SubsiteArity 1) rc
            [| True |])

spec :: Spec
spec = describe "deep nested-route arity guard (reify path)" $ do
    it "reifies datatype arities used by the guard" $
        reifiedArities `shouldBe` (1, 1, 0)
    it "catches a deep arity mismatch with the actionable guard (not a kind error)" $
        wrongArityCaught `shouldBe` True
    it "lets a correctly-parameterized deep nested datatype through" $
        matchingArityOk `shouldBe` True