servant-util-0.1.0: src/Servant/Util/Combinators/Sorting/Server.hs
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Servant.Util.Combinators.Sorting.Server () where
import Universum
import Data.Char (isAlphaNum)
import qualified Data.List as L
import qualified Data.Set as S
import Servant.API (FromHttpApiData (..), (:>))
import Servant.Server (DefaultErrorFormatters, ErrorFormatters, HasServer (..), Tagged (..),
unTagged)
import Servant.Server.Internal.Context (HasContextEntry, type (.++))
import Text.Megaparsec ((<?>))
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
import Servant.Util.Combinators.Sorting.Base
import Servant.Util.Common
-- | Ensure no name in entries repeat.
sortingCheckDuplicates :: [SortingItem] -> Either Text ()
sortingCheckDuplicates items =
let names = map siName items
duplicate = safeHead . mapMaybe (safeHead . drop 1) . L.group $ sort names
in maybe pass (\n -> Left $ "Duplicated field " <> show n) duplicate
-- | Consumes "sortBy" query parameter and fetches sorting parameters contained in it.
instance ( HasServer subApi ctx
, HasContextEntry (ctx .++ DefaultErrorFormatters) ErrorFormatters
, ReifySortingItems base
, ReifyParamsNames provided
) =>
HasServer (SortingParams provided base :> subApi) ctx where
type ServerT (SortingParams provided base :> subApi) m =
SortingSpec provided base -> ServerT subApi m
route =
inRouteServer @(SortParamsExpanded provided subApi) route $
\handler rawSortItems -> handler (SortingSpec $ fmap unTagged rawSortItems ?: [])
hoistServerWithContext _ pc nt s =
hoistServerWithContext (Proxy @subApi) pc nt . s
-- | Parse 'sort_by' query param.
-- Following the format described in "Sorting" section of https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sorting-and-Pagination/
instance ReifyParamsNames allowed =>
FromHttpApiData (TaggedSortingItemsList allowed) where
parseUrlPiece =
first (toText . P.errorBundlePretty) . second Tagged .
P.parse parser "sortBy"
where
parser = do
items <- P.sepBy itemParser (P.char ',')
either (fail . toString) pure $ sortingCheckDuplicates items
P.eof
return items
itemParser :: P.Parsec Void Text SortingItem
itemParser = asum
[ do
siOrder <- asum
[ Ascendant <$ P.char '+'
, Descendant <$ P.char '-'
] <?> "ordering sign (+/-)"
siName <- paramNameParser
return SortingItem{..}
, do
siOrder <- asum
[ Ascendant <$ P.string' "asc"
, Descendant <$ P.string' "desc"
] <?> "ordering keyword (asc/desc)"
siName <- P.char '(' *> paramNameParser <* P.char ')'
return SortingItem{..}
]
allowedParams = reifyParamsNamesSet @allowed
paramNameParser = do
name <- P.takeWhile1P (Just "sorting item name") isAlphaNum <?> "parameter name"
unless (name `S.member` allowedParams) $
fail $ "unknown parameter " <> show name <>
" (expected one of " <> show (toList allowedParams) <> ")"
return name