packages feed

yesod-core-1.7.0.0: test/YesodCoreTest/ParamNoExplicitArgs.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | Regression pin for invoking @mkYesod@ on a *parameterized* foundation
-- /without/ writing its explicit type argument (here @mkYesod "PNoArgs"@ for
-- @data PNoArgs a@). The reified arity is filled with a fresh type variable,
-- and that fill var must travel in the 'TyArgs' handed to 'discoveryMode' /
-- the generators — otherwise the site is misclassified as monomorphic and
-- emits ill-scoped nested instances (a 1.6 -> 1.7 regression). With the fix it
-- stays on the backwards-compatible inline path. The build is the assertion;
-- the WAI rows confirm it still dispatches.
module YesodCoreTest.ParamNoExplicitArgs
    ( specs
    ) where

import Data.Text (Text)
import Test.Hspec
import qualified Data.ByteString.Lazy as L
import qualified Network.HTTP.Types as H
import Yesod.Core

import YesodCoreTest.RuntimeHarness (assertRequestFor)

-- One type argument, but the splice below names only "PNoArgs" (no @a@).
data PNoArgs a = PNoArgs

-- @CatchAllR@ overlaps @SubParentR@'s prefix (both start with @sub@), so its
-- @!@ turns overlap checking off for it. It is placed AFTER the parent on
-- purpose: under the historical (1.6) inline @parseRoute@ semantics a path that
-- matches the @sub@ prefix but misses every child must COMMIT at the parent and
-- return 'Nothing', NOT fall through to this later, lower-priority sibling. That
-- is exactly what inline @dispatch@ does (404), so @parseRoute@ has to agree.
mkYesod "PNoArgs" [parseRoutes|
/ HomeR GET
/sub SubParentR:
    / SubHomeR GET
!/sub/#Text CatchAllR GET
|]

instance Yesod (PNoArgs a) where
    messageLoggerSource = mempty

getHomeR :: HandlerFor (PNoArgs a) Text
getHomeR = pure "home"

getSubHomeR :: HandlerFor (PNoArgs a) Text
getSubHomeR = pure "subHome"

getCatchAllR :: Text -> HandlerFor (PNoArgs a) Text
getCatchAllR _ = pure "catchAll"

testRequestIO
    :: HasCallStack
    => Int
    -> [Text]
    -> H.Method
    -> Maybe L.ByteString
    -> IO ()
testRequestIO status path method mexpected =
    assertRequestFor (PNoArgs :: PNoArgs ()) method status path mexpected

specs :: Spec
specs = describe "parameterized foundation invoked without explicit type args" $ do
    it "static leaf dispatches" $
        testRequestIO 200 [] "GET" (Just "home")
    it "inlined nested leaf dispatches" $
        testRequestIO 200 ["sub"] "GET" (Just "subHome")
    it "404 on unknown nested suffix (dispatch commits at the parent prefix)" $
        testRequestIO 404 ["sub", "oops"] "GET" Nothing
    it "parseRoute commits at the parent prefix: a child miss is Nothing, not the later overlapping sibling" $
        -- The regression: with overlap checking off the flat inline codegen let
        -- this fall through to @CatchAllR@, so parseRoute returned @Just
        -- (CatchAllR "oops")@ while dispatch returned 404 — they disagreed for
        -- the same request. Commit-on-parent-prefix makes parseRoute agree.
        (parseRoute (["sub", "oops"], []) :: Maybe (Route (PNoArgs ())))
            `shouldBe` Nothing
    it "parseRoute still reaches a matching child under the parent prefix" $
        (parseRoute (["sub"], []) :: Maybe (Route (PNoArgs ())))
            `shouldBe` Just (SubParentR SubHomeR)