servant-util-0.1.0: src/Servant/Util/Dummy/Filtering.hs
{- | Implements plain filtering.
Example:
@
filteringSpecApp
:: MyObject
-> FilteringSpecApp
DummyFilteringBackend
[ "id" ?: 'AutoFilter Course
, "desc" ?: 'AutoFilter Text
, "isAwesome" ?: 'ManualFilter Bool
]
filteringSpecApp obj =
filterOn_ @"id" (id obj) .*.
filterOn_ @"desc" (desc obj) .*.
customFilter_ @"isAwesome" (== (awesomeness obj > 42)) .*.
HNil
@
Annotating 'filterOn' and 'customFilter' calls with parameter name is fully optional
and used only to visually disambiguate filters of the same types.
Next, you use `matches` to check whether a value matches user-provided filters.
@
filterObjects filters = filter (matches filters . filteringSpecApp) allObjects
@
-}
module Servant.Util.Dummy.Filtering
( matches
, filterBySpec
, filterOn
, manualFilter
) where
import Universum
import Data.Bits ((.|.))
import qualified Text.Regex.Posix as R
import qualified Text.Regex.Posix.String as RS
import Servant.Util.Combinators.Filtering.Backend
import Servant.Util.Combinators.Filtering.Base
import Servant.Util.Combinators.Filtering.Filters
-- | Implements filters via Beam query expressions ('QExpr').
data DummyFilteringBackend
instance FilterBackend DummyFilteringBackend where
type AutoFilteredValue DummyFilteringBackend a = a
type MatchPredicate DummyFilteringBackend = Bool
instance Eq a => AutoFilterSupport DummyFilteringBackend FilterMatching a where
autoFilterSupport = \case
FilterMatching v -> (== v)
FilterNotMatching v -> (/= v)
FilterItemsIn vs -> (`elem` vs)
instance Ord a => AutoFilterSupport DummyFilteringBackend FilterComparing a where
autoFilterSupport = \case
FilterGT v -> (> v)
FilterLT v -> (< v)
FilterGTE v -> (>= v)
FilterLTE v -> (<= v)
-- | Supported only for trivial cases yet.
instance ToString s => AutoFilterSupport DummyFilteringBackend FilterLike s where
autoFilterSupport
(FilterLike (CaseSensitivity cs) (LikePatternUnsafe (toString -> pat))) =
\(toString -> txt) -> isJust @() $ do
let compOpts = RS.compBlank
& if cs then id else (.|. RS.compIgnoreCase)
-- TODO: report this to servant parser
regex <- R.makeRegexOptsM compOpts RS.execBlank (transformPat pat)
R.matchM regex txt
where
transformPat s = '^' : replacePatChars s ++ "$"
replacePatChars = \case
[] -> []
'*' : s -> '.' : '*' : replacePatChars s
c : s -> c : replacePatChars s
-- | Applies a whole filtering specification to a set of response fields.
-- Resulting value can be put to 'filter' function.
matches
:: ( backend ~ DummyFilteringBackend
, BackendApplySomeFilter backend params
)
=> FilteringSpec params
-> FilteringSpecApp backend params
-> Bool
matches = and ... backendApplyFilters
-- | Filters given values by a filtering specification.
filterBySpec
:: ( backend ~ DummyFilteringBackend
, BackendApplySomeFilter backend params
)
=> FilteringSpec params
-> (a -> FilteringSpecApp backend params)
-> [a]
-> [a]
filterBySpec spec mkApp = filter (matches spec . mkApp)