{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TemplateHaskell #-} module Yesod.Filter.TH ( mkFilterGenerator , mkFilters , mkSelectOpts -- Types , Options (..) , Filtering (..) , FilterDef (..) , FilterParam (..) , FilterOp (..) , Sorting (..) , SortOrdering (..) , SortDirection (..) , Pagination (..) , PageLimit (..) , PageOffset (..) , defaultOptions , defaultFiltering , defaultFilterParams , defaultSorting , defaultPagination -- for testing , mkToFilterValueInstances , availableFiltersE , defaultOrderByE , availableOrderBysE ) where import Control.Monad ((>=>)) import Data.Text (Text, cons, pack, unpack) import Data.Time (Day, TimeOfDay, UTCTime) import Database.Persist (BackendKey) import Database.Persist.Sql (SqlBackend) import Language.Haskell.TH (DecsQ, ExpQ, conE, conT, listE, mkName) import Text.Read (readMaybe) import Yesod.Core (MonadHandler) import Yesod.Persist (Filter, Key, SelectOpt (Asc, Desc), (!=.), (<.), (<=.), (==.), (>.), (>=.)) import Yesod.Filter.Builder (buildFiltersFromGetParams, buildSelectOptsFromGetParams) import Yesod.Filter.Read (capitalize, readMaybeBool, readMaybeDay, readMaybeDouble, readMaybeInt, readMaybeTimeOfDay, readMaybeUTCTime) import Yesod.Filter.Types -- ExpQ: [| [Filter record] |] -- | Generates the list of `Filter`. mkFilters :: ExpQ mkFilters = [| filtersFromGetParams |] -- ExpQ: [| [SelectOpt record] |] -- | Generates the list of `SelectOpt`. mkSelectOpts :: ExpQ mkSelectOpts = [| selectOptsFromGetParams |] -- | Generates the function that creates the list of `Filter` and the list of `SelectOpt` from query parameters. mkFilterGenerator :: Text -> Options -> DecsQ mkFilterGenerator model options = concat <$> sequence [ mkToFilterValueInstances model , mkFiltersFromGetParams model options , mkSelectOptsFromGetParams model options ] mkToFilterValueInstances :: Text -> DecsQ mkToFilterValueInstances model = [d| class ToKey a where toKey :: BackendKey SqlBackend -> Key a instance ToKey $(conT $ mkName (unpack model)) where toKey = $(conE $ mkName $ unpack model ++ "Key") class ToFilterValue a where toFilterValue :: Text -> Maybe a instance ToKey record => ToFilterValue (Key record) where toFilterValue v = case readMaybe (unpack v) of Just n -> Just (toKey n) Nothing -> Nothing instance ToFilterValue Text where toFilterValue = Just instance ToFilterValue Int where toFilterValue = readMaybeInt . unpack instance ToFilterValue Double where toFilterValue = readMaybeDouble . unpack instance ToFilterValue Bool where toFilterValue = readMaybeBool . unpack instance ToFilterValue Day where toFilterValue = readMaybeDay . unpack instance ToFilterValue TimeOfDay where toFilterValue = readMaybeTimeOfDay . unpack instance ToFilterValue UTCTime where toFilterValue = readMaybeUTCTime . unpack instance ToFilterValue a => ToFilterValue (Maybe a) where toFilterValue = toFilterValue >=> Just . Just |] mkFiltersFromGetParams :: Text -> Options -> DecsQ mkFiltersFromGetParams model options = [d| filtersFromGetParams :: MonadHandler m => m [Filter $(conT $ mkName (unpack model))] filtersFromGetParams = buildFiltersFromGetParams $(availableFiltersE model $ filtering options) $([| options |]) |] -- ExpQ: [| [(Text, Text -> Maybe (Filter record))] |] availableFiltersE :: Text -> Filtering -> ExpQ availableFiltersE model (SimpleFiltering defs) = [| $(listE $ concatMap availableFilterE' defs) |] where availableFilterE' (FilterDef field filterParams) = map (availableFilterE model field) filterParams availableFiltersE _ _ = [| [] |] -- ExpQ: [| (Text, Text -> Maybe (Filter record)) |] availableFilterE :: Text -> Text -> FilterParam -> ExpQ availableFilterE model field (CustomParam op param) = [| (param, $(filterBuilderE model field op)) |] availableFilterE model field (AutoParam op) = [| (defaultParam, $(filterBuilderE model field op)) |] where defaultParam = case op of EqOp -> field NeOp -> field <> pack "__ne" GtOp -> field <> pack "__gt" LtOp -> field <> pack "__lt" GeOp -> field <> pack "__ge" LeOp -> field <> pack "__le" IsNullOp -> field <> pack "__isnull" -- ExpQ: [| Text -> Maybe (Filter record) |] filterBuilderE :: Text -> Text -> FilterOp -> ExpQ filterBuilderE model field EqOp = [| toFilterValue >=> (Just . (==.) $(entityFieldE model field)) |] filterBuilderE model field NeOp = [| toFilterValue >=> (Just . (!=.) $(entityFieldE model field)) |] filterBuilderE model field GtOp = [| toFilterValue >=> (Just . (>.) $(entityFieldE model field)) |] filterBuilderE model field LtOp = [| toFilterValue >=> (Just . (<.) $(entityFieldE model field)) |] filterBuilderE model field GeOp = [| toFilterValue >=> (Just . (>=.) $(entityFieldE model field)) |] filterBuilderE model field LeOp = [| toFilterValue >=> (Just . (<=.) $(entityFieldE model field)) |] filterBuilderE model field IsNullOp = [| toFilterValue >=> (\b -> Just $ (if b then (==.) else (!=.)) $(entityFieldE model field) Nothing) |] mkSelectOptsFromGetParams :: Text -> Options -> DecsQ mkSelectOptsFromGetParams model options = [d| selectOptsFromGetParams :: MonadHandler m => m [SelectOpt $(conT $ mkName (unpack model))] selectOptsFromGetParams = buildSelectOptsFromGetParams $(defaultOrderByE model $ sorting options) $(availableOrderBysE model $ sorting options) $([| options |]) |] -- ExpQ: [| Maybe (SelectOpt record) |] defaultOrderByE :: Text -> Sorting -> ExpQ defaultOrderByE model (AllowSorting _ _ (ORDERBY field ASC)) = [| Just $ Asc $(entityFieldE model field) |] defaultOrderByE model (AllowSorting _ _ (ORDERBY field DESC)) = [| Just $ Desc $(entityFieldE model field) |] defaultOrderByE _ _ = [| Nothing |] -- ExpQ: [| [(Text, SelectOpt record)] |] availableOrderBysE :: Text -> Sorting -> ExpQ availableOrderBysE model (AllowSorting _ fields _) = [| $(listE $ map asc fields) ++ $(listE $ map desc fields) |] where asc field = [| (field, Asc $(entityFieldE model field)) |] desc field = [| (cons '-' field, Desc $(entityFieldE model field)) |] availableOrderBysE _ _ = [| [] |] -- ExpQ: [| EntityField record typ |] entityFieldE :: Text -> Text-> ExpQ entityFieldE model field = conE $ mkName $ unpack model ++ capitalize (unpack field)