{-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -- | An internal module. Depend on this at your own risk -- breaking changes to -- this module's interface will not be represented as a major version bump. module Rowdy.Yesod.Internal where import Data.Char (toUpper) import Data.Either (isLeft, lefts, rights) import Data.Maybe (isJust) import Data.String (IsString (..)) import Data.Typeable (Proxy (..), Typeable, eqT, typeRep) import Yesod.Routes.TH.Types import Rowdy -- | An endpoint in the Yesod model. data Endpoint = MkResource Verb String -- ^ A resource identified by a 'Verb' and a 'String' name. | MkSubsite String String String -- ^ A subsite. deriving (Eq, Show) -- | The type of things that can affect a path. data PathPiece = Literal String -- ^ Static string literals. | Capture Type -- ^ Dynamic captures. | Attr String -- ^ Route attributes. Not technically part of the path, but applies to -- everything below it in the tree. deriving (Eq, Show) instance IsString PathPiece where fromString = Literal -- | A value containing a 'Proxy' of some Haskell type. data Type where Type :: Typeable t => Proxy t -> Type instance Show Type where show (Type prxy) = show (typeRep prxy) instance Eq Type where Type (_ :: Proxy t0) == Type (_ :: Proxy t1) = isJust (eqT @t0 @t1) -- | The HTTP verbs. data Verb = Get | Put | Post | Delete deriving (Eq, Show) -- | Render a verb as an uppercase string. renderVerb :: Verb -> String renderVerb = map toUpper . show -- | Convert the Rowdy 'RouteTree' structure into one appropriate for the Yesod -- routing functions. routeTreeToResourceTree :: [RouteTree String PathPiece Endpoint] -> [ResourceTree String] routeTreeToResourceTree = foldr (go []) [] where go :: [Either String (Piece String)] -> RouteTree String PathPiece Endpoint -> [ResourceTree String] -> [ResourceTree String] go pcs (Nest str xs) acc = ResourceParent str True pieces (foldr (go attrs) [] xs) : acc where pieces = rights (reverse pcs) attrs = filter isLeft pcs go pcs (PathComponent pp rest) acc = go (convPiece pp : pcs) rest acc go pcs (Leaf term) (ResourceLeaf Resource {..} : acc) | listEq eqPieceStr (rights (reverse pcs)) resourcePieces , Methods multi methods <- resourceDispatch , MkResource _ endpointName <- term , resourceName == endpointName = flip (:) acc . ResourceLeaf $ case term of MkResource v str -> Resource { resourceName = str , resourcePieces = rights (reverse pcs) , resourceDispatch = Methods { methodsMulti = multi , methodsMethods = renderVerb v : methods } , resourceAttrs = lefts pcs , resourceCheck = True } MkSubsite str typ func -> Resource { resourceName = str , resourcePieces = reverse (rights pcs) , resourceDispatch = Subsite { subsiteType = typ , subsiteFunc = func } , resourceAttrs = lefts pcs , resourceCheck = True } go pcs (Leaf term) acc = flip (:) acc . ResourceLeaf $ case term of MkResource v str -> Resource { resourceName = str , resourcePieces = reverse (rights pcs) , resourceDispatch = Methods { methodsMulti = Nothing , methodsMethods = [renderVerb v] } , resourceAttrs = lefts pcs , resourceCheck = True } MkSubsite str typ func -> Resource { resourceName = str , resourcePieces = reverse (rights pcs) , resourceDispatch = Subsite { subsiteType = typ , subsiteFunc = func } , resourceAttrs = lefts pcs , resourceCheck = True } convPiece :: PathPiece -> Either String (Piece String) convPiece = \case Literal str -> Right (Static str) Capture (Type prxy) -> Right (Dynamic (show (typeRep prxy))) Attr attr -> Left attr listEq :: (a -> a -> Bool) -> [a] -> [a] -> Bool listEq f (x:xs) (y:ys) = f x y && listEq f xs ys listEq _ [] [] = True listEq _ _ _ = False eqPieceStr :: Piece String -> Piece String -> Bool eqPieceStr (Static s2) (Static s1) = s1 == s2 eqPieceStr (Dynamic d0) (Dynamic d1) = d0 == d1 eqPieceStr _ _ = False