rowdy-yesod-0.0.1.0: src/Rowdy/Yesod/Internal.hs
{-# 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
=
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