servant-util-0.1.0: src/Servant/Util/Combinators/Sorting/Backend.hs
-- | Applying sorting specifications.
module Servant.Util.Combinators.Sorting.Backend
( SortingBackend (..)
, fieldSort
, SortingApp (..)
, SortingSpecApp
, ApplyToSortItem (..)
, backendApplySorting
) where
import Universum
import GHC.TypeLits (KnownSymbol)
import Servant.Util.Combinators.Sorting.Base
import Servant.Util.Common
-- | Implementation of filtering backend.
class SortingBackend backend where
-- | The part of object which we are filtering on,
-- provided by server backend implementor.
type SortedValue backend a :: *
-- | What we require from sorted values in order to be sortable.
type SortedValueConstraint backend a :: Constraint
type SortedValueConstraint backend a = ()
-- | A resulting ordering.
type BackendOrdering backend :: *
-- | Implement 'SortingApp' as sorting on the given field.
backendFieldSort
:: SortedValueConstraint backend a
=> SortedValue backend a
-> SortingApp backend ('TyNamedParam name a)
fieldSort
:: forall name a backend.
(SortingBackend backend, SortedValueConstraint backend a)
=> SortedValue backend a -> SortingApp backend ('TyNamedParam name a)
fieldSort = backendFieldSort
-- | A function defining a way to apply the given 'SortingItem' (which is sorting
-- order on a single parameter).
newtype SortingApp backend param
= SortingApp (SortingItemTagged param -> BackendOrdering backend)
{- | List of 'SortingApp' functions. Describes how to apply @SortingSpec params@
(each of possible 'SortingItem') to an SQL query.
Instance of this type can be created using 'fieldSort' function. For example:
@
sortingSpecApp :: SortingSpecApp ["course" ?: Course, "desc" ?: Text]
sortingSpecApp =
fieldSort @"course" courseField .*.
fieldSort @"desc" descField .*.
HNil
@
Annotating 'fieldSort' call with parameter name is not mandatory but recommended
to prevent possible mistakes in 'fieldSort's ordering.
-}
type SortingSpecApp backend (allParams :: [TyNamedParam *]) =
HList (SortingApp backend) allParams
-- | Lookup for appropriate 'SortingApp' in 'SortingSpecApp' and apply it to 'SortingItem'.
class ApplyToSortItem backend params where
-- | Apply spec app to the given 'SortingItem'
-- We return 'Maybe' here (instead of forcing presence at type-level) for convenience.
applyToSortItem
:: SortingSpecApp backend params
-> SortingItem
-> Maybe (BackendOrdering backend)
instance ApplyToSortItem backend '[] where
applyToSortItem HNil _ = Nothing
instance (KnownSymbol name, ApplyToSortItem backend params) =>
ApplyToSortItem backend ('TyNamedParam name p ': params) where
applyToSortItem (SortingApp app `HCons` appRem) item = asum
[ guard (symbolValT @name == siName item) $> app (SortingItemTagged item)
, applyToSortItem @backend @params appRem item
]
-- | Apply a given 'SortingSpecApp' to a 'SortingSpec' producing a pack of
-- ordering values which define lexicographical sorting order.
backendApplySorting
:: forall provided base allParams backend.
( allParams ~ AllSortingParams provided base
, ApplyToSortItem backend allParams
)
=> SortingSpec provided base
-> SortingSpecApp backend allParams
-> [BackendOrdering backend]
backendApplySorting spec app =
ssAll spec <&> \sitem ->
applyToSortItem @backend @allParams app sitem
-- impossible due to invariants of 'SortingSpec'
?: error ("Impossible: don't know how to apply to spec item " <> show sitem)