{-# LANGUAGE TypeOperators,
             ConstraintKinds,
             MultiParamTypeClasses,
             TypeFamilies,
             TypeSynonymInstances,
             DataKinds,
             FlexibleContexts,
             FlexibleInstances,
             ScopedTypeVariables #-}
{- |
Module      :  Servant.Prelude
Copyright   :  (c) Zalora SEA 2014
License     :  BSD3
Maintainer  :  Alp Mestanogullari <alp@zalora.com>
Stability   :  experimental

Some standard REST-y operations your 'Resource's can
support out of the box.

Your type will have to implement a couple of class instances
to be usable with a backend. For the scotty backend, this means
having 'FromJSON' or 'ToJSON' instances and the appropriate
'toResponse' implementations for the return types of your
database.
-}
module Servant.Prelude
 ( -- * Defining 'Resource's
   module Servant.Resource
 , module Servant.Context
 , module Servant.Error
 
 , -- * Standard operations
   Add
 , addWith
 , Delete
 , deleteWith
 , ListAll
 , listAllWith
 , Update
 , updateWith
 , View
 , viewWith
 ) where

import Servant.Context
import Servant.Error
import Servant.Resource

-- | A dummy type representing an operation that adds an entry.
data Add
instance Show Add where show _ = "Add"

-- | A dummy type representing an operation that deletes an entry.
data Delete
instance Show Delete where show _ = "Delete"

-- | A dummy type representing an operation that lists all entries.
data ListAll
instance Show ListAll where show _ = "ListAll"

-- | A dummy type representing an operation that updates an entry.
data Update
instance Show Update where show _ = "Update"

-- | A dummy type representing an operation that looks up an entry.
data View
instance Show View where show _ = "View"

-- | To 'Add' an entry, we require a function of type @c -> a -> IO r@.
type instance Operation Add c a i r     =      a -> c -> IO (r Add)
-- | To 'Delete' an entry, we require a function of type @c -> i -> IO r@.
type instance Operation Delete c a i r  = i      -> c -> IO (r Delete)
-- | To 'List' all entries, we require a function of type @c -> IO [a]@.
type instance Operation ListAll c a i r =           c -> IO [a]
-- | To 'Update' an entry, we require a function of type @c -> i -> a -> IO r@.
type instance Operation Update c a i r  = i -> a -> c -> IO (r Update)
-- | To 'View' an entry, we require a function of type @c -> i -> IO (Maybe a)@.
type instance Operation View c a i r    = i      -> c -> IO (Maybe a)

-- | Make a 'Resource' support the 'Add'
--   operation by using your function.
--
--   Given:
--
--   > addPerson :: PGSQL.Connection -> Person -> IO Bool
--
--   you could do:
--
--   > mkResource "persons" postgresContext noErrorHandling
--   >   & addWith addPerson
addWith :: Contains Add ops ~ False
        => (a -> c -> IO (r Add))
        -> Resource c a i r e ops
        -> Resource c a i r e (Add ': ops)
addWith = addOperation

-- | Make a 'Resource' support the 'Delete'
--   operation by using your function.
--
--   Given:
--
--   > deletePerson :: PGSQL.Connection -> PersonId -> IO Bool
--
--   you could do:
--
--   > mkResource "persons" postgresContext noErrorHandling
--   >   & deleteWith addPerson
deleteWith :: Contains Delete ops ~ False
           => (i -> c -> IO (r Delete))
           -> Resource c a i r e ops
           -> Resource c a i r e (Delete ': ops)
deleteWith = addOperation

-- | Make a 'Resource' support the 'ListAll'
--   operation by using your function.
--
--   Given:
--
--   > listAllPersons :: PGSQL.Connection -> IO [Person]
--
--   you could do:
--
--   > mkResource "persons" postgresContext noErrorHandling
--   >   & listAllWith listAllPersons
listAllWith :: Contains ListAll ops ~ False
            => (c -> IO [a])
            -> Resource c a i r e ops
            -> Resource c a i r e (ListAll ': ops)
listAllWith = addOperation

-- | Make a 'Resource' support the 'Update'
--   operation by using your function.
--
--   Given:
--
--   > updatePerson :: PGSQL.Connection -> PersonId -> Person -> IO Bool
--
--   you could do:
--
--   > mkResource "persons" postgresContext noErrorHandling
--   >   & updateWith updatePerson
updateWith :: Contains Update ops ~ False
           => (i -> a -> c -> IO (r Update))
           -> Resource c a i r e ops
           -> Resource c a i r e (Update ': ops)
updateWith = addOperation

-- | Make a 'Resource' support the 'View'
--   operation by using your function.
--
--   Given:
--
--   > viewPerson :: PGSQL.Connection -> PersonId -> IO (Maybe Person)
--
--   you could do:
--
--   > mkResource "persons" postgresContext noErrorHandling
--   >   & viewWith viewPerson
viewWith :: Contains View ops ~ False
         => (i -> c -> IO (Maybe a))
         -> Resource c a i r e ops
         -> Resource c a i r e (View ': ops)
viewWith = addOperation