{-# LANGUAGE OverloadedStrings #-} -- | DataTables request parsing. module Yesod.DataTables.Request (Request(..), Column(..), ColumnName, SortDir(..), parseRequest) where import Prelude import Data.Aeson as J import Data.Attoparsec (parse, maybeResult) import Data.List as L import Data.Maybe import Data.Text as T import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 import Data.Text.Encoding as E -- | HTTP request (GET or POST) parameter name type ParamName = Text -- | HTTP request (GET or POST) parameter value type ParamValue = Text -- | enum for sSortDir_(int) data SortDir = SortAsc | SortDesc deriving (Eq, Show) -- | Name of DataTables grid column type ColumnName = Text -- | information about grid column data Column = Column { -- | whether searching is enabled at client-side colSearchable :: Bool, -- | column-specific search query colSearch :: Text, -- | whether search query should be interpreted as a regular expression colSearchRegex :: Bool, -- | whether sorting is enabled at client-side colSortable :: Bool, -- | column name (client-side also expects the data in a field with the -- same name colName :: Text } deriving (Show, Eq) -- | DataTables grid server-side request -- (see ) data Request = Request { -- | Display start point in the current data set. reqDisplayStart :: Int, -- | Number of records that the table can display in the current draw. It is expected that the number of records returned will be equal to this number, unless the server has fewer records to return. reqDisplayLength :: Int, -- | Global search field reqSearch :: Text, -- | True if the global filter should be treated as a regular expression for advanced filtering, false if not. reqSearchRegex :: Bool, -- | columns that the client-side knows about reqColumns :: [Column], -- | result set sorting instructions reqSort :: [(ColumnName,SortDir)], -- | Information for DataTables to use for rendering (do not alter). reqEcho :: Int } deriving (Show, Eq) readMaybe :: (Read a) => Maybe Text -> Maybe a readMaybe (Just s) = case reads (unpack s) of [(x, "")] -> Just x _ -> Nothing readMaybe _ = Nothing readBool :: Maybe Text -> Maybe Bool readBool (Just "true") = Just True readBool (Just "false") = Just False readBool _ = Nothing parseColumn :: Text -> Text -> Text -> Text -> Text -> Maybe Column parseColumn searchable' search regex' sortable' dataProp = do searchable <- readBool $ Just searchable' regex <- readBool $ Just regex' sortable <- readBool $ Just sortable' return $ Column { colSearchable = searchable, colSearch = search, colSearchRegex = regex, colSortable = sortable, colName = dataProp } checkColumns :: [Maybe Column] -> Int -> Maybe [Column] checkColumns mcolumns nColumns= let columns = catMaybes mcolumns in if L.length columns == nColumns then Just columns else Nothing readSortDir :: Text -> Maybe SortDir readSortDir "asc" = Just SortAsc readSortDir "desc" = Just SortDesc readSortDir _ = Nothing parseSortDir :: [Column] -> Text -> Text -> Maybe (ColumnName, SortDir) parseSortDir columns idStr sortDir = do idNum <- readMaybe (Just idStr) name <- maybeColumnName idNum dir <- readSortDir sortDir return (name, dir) where maybeColumnName colId | colId < 0 = Nothing | colId >= L.length columns = Nothing | otherwise = Just $ colName (columns !! colId) -- | Tries to parse DataTables request parseRequest :: [(ParamName, ParamValue)] -> Maybe Request parseRequest params = do displayStart <- readMaybe $ param "iDisplayStart" displayLength <- readMaybe $ param "iDisplayLength" nColumns <- readMaybe $ param "iColumns" search <- param "sSearch" regex <- readBool $ param "bRegex" cSearchable <- manyParams "bSearchable_" nColumns cSearch <- manyParams "sSearch_" nColumns cRegex <- manyParams "bRegex_" nColumns cSortable <- manyParams "bSortable_" nColumns cName <- manyParams "mDataProp_" nColumns let columnData = L.zipWith5 parseColumn cSearchable cSearch cRegex cSortable cName columns <- checkColumns columnData nColumns nSortingCols <- readMaybe $ param "iSortingCols" sortingCols <- manyParams "iSortCol_" nSortingCols sortingColsDir <- manyParams "sSortDir_" nSortingCols echo <- readMaybe $ param "sEcho" let sortInfo = catMaybes $ L.zipWith (parseSortDir columns) sortingCols sortingColsDir return $ Request { reqDisplayStart = displayStart, reqDisplayLength = displayLength, reqSearch = search, reqSearchRegex = regex, reqColumns = columns, reqSort = sortInfo, reqEcho = echo } where param :: ParamName -> Maybe ParamValue param key = lookup key params manyParams :: ParamName -> Int -> Maybe [ParamValue] manyParams key num = let values = catMaybes $ L.map param [ T.concat [key, pack $ show n] | n <- [0..num-1] ] numValues = L.length values in if numValues == num then Just values else Nothing