packages feed

servant-util-0.4: src/Servant/Util/Combinators/Sorting/Construction.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}

-- | Manual construction of sorting spec.
module Servant.Util.Combinators.Sorting.Construction
    ( SortingRequestItem
    , asc, desc
    , mkSortingSpec
    , noSorting
    ) where

import Universum

import Data.Default (Default (..))
import qualified GHC.Exts
import GHC.Exts (IsList)
import GHC.TypeLits (ErrorMessage (..), KnownSymbol, Symbol, TypeError)

import Servant.Util.Combinators.Sorting.Base
import Servant.Util.Common


-- | Helper for defining custom 'SortingSpec's,
-- contains 'SortingItem' corresponding to one of parameter in @provided@ list.
newtype SortingRequestItem (provided :: [TyNamedParam *]) = SortingRequestItem
    { unSortingRequestItem :: SortingItem
    } deriving (Show)

type family KnownTypeName
    (origProvided :: [TyNamedParam *])
    (name :: Symbol)
    (provided :: [TyNamedParam *])
        :: Constraint where
    KnownTypeName orig name '[] =
        TypeError ('Text "Parameter " ':<>: 'ShowType name ':<>: 'Text " is not allowed here"
                   ':$$: 'Text "Available fields to sort on: " ':<>:
                         'ShowType (TyNamedParamsNames orig))
    KnownTypeName _ name ('TyNamedParam name _ ': _) = (KnownSymbol name)
    KnownTypeName orig name ('TyNamedParam name0 _ ': provided) = KnownTypeName orig name provided

-- | Ascendant sorting on a field with given name.
asc
    :: forall name provided.
       (KnownSymbol name, KnownTypeName provided name provided)
    => NameLabel name -> SortingRequestItem provided
asc _ = SortingRequestItem SortingItem
      { siName = symbolValT @name
      , siOrder = Ascendant
      }

-- | Ascendant sorting on a field with given name.
desc
    :: forall name provided.
       (KnownSymbol name, KnownTypeName provided name provided)
    => NameLabel name -> SortingRequestItem provided
desc _ = SortingRequestItem SortingItem
      { siName = symbolValT @name
      , siOrder = Descendant
      }

-- | Instance for 'SortingSpec' construction.
instance ReifySortingItems base => IsList (SortingSpec provided base) where
    type Item (SortingSpec provided base) = SortingRequestItem provided
    toList = map SortingRequestItem . ssProvided
    fromList = SortingSpec . map unSortingRequestItem

{- | Make a sorting specification.
Specified list should contain sorting on distinct fields; we do not enforce this
at type-level for convenience.

Example:

@
-- {-# LANGUAGE OverloadedLabels #-}

sortingSpec :: SortingSpec ["id" ?: Int, "desc" ?: Text]
sortingSpec = mkSortingSpec [asc #id]
@

-}
mkSortingSpec
    :: ReifySortingItems base
    => [SortingRequestItem provided] -> SortingSpec provided base
mkSortingSpec = GHC.Exts.fromList

-- | By default 'noSorting' is used.
instance ReifySortingItems base => Default (SortingSpec provided base) where
    def = noSorting

-- | Do not specify ordering.
noSorting :: ReifySortingItems base => SortingSpec provided base
noSorting = mkSortingSpec []