pg-entity-0.0.4.1: docs/src/ErrorsGuide.hs
module ErrorsGuide where
import Control.Monad.Except (MonadError (..))
import Control.Monad.Trans
import Data.Text (Text)
import Database.PostgreSQL.Entity (Entity, selectById)
import Database.PostgreSQL.Entity.Types (GenericEntity)
import Database.PostgreSQL.Simple
import Database.PostgreSQL.Transact (DBT)
import GHC.Generics (Generic)
-- ## Error Handling
-- Error handling is a tricky subject, and most often you will have to provide translation layers between your different components to express how a request
-- has failed and what is the relevant information to be reported.
-- For example, inserting twice the same entity with the same primary key will raise an error in the database engine that you have violated the uniqueness
-- constraint of a primary key. This is of little use for consumers of the system, who simply need to be told that their chosen email address or username is already used.
-- Building a top-down error datatype can be a very good or very bad idea, and should sometimes be replaced with a more extensible mechanism like Haskell
-- Exceptions (whose datatypes can be used outside of this mechanism, fortunately).
-- Let us consider a simple usecase, where we wish to express the following error modes:
-- * Entity was not found
-- * Entity is in a Bad State™
-- * Entity processing is running
data EntityError
= EntityNotFound
| EntityBadState
| EntityProcessingIsRunning
deriving (Eq, Show)
-- A [`MonadError`][MonadError] stack can be used to handle errors with your data-type.
-- Here are the functions that we will be using:
-- This function allows great control over the way we report errors,
-- and allows us to plug a `MonadError` for reporting.
-- ```haskell
-- withPool :: (MonadBaseControl IO m)
-- => Pool Connection
-- -> DBT m a
-- -> m a
-- ```
-- Those two functions show that we do not have to put a `MonadError` everywhere, and if a lower-level error happens, we can let it bubble up to create a
-- higher-level error (like status code 500 in an http server).
data E = E
{ eId :: Text
}
deriving stock (Eq, Show, Generic)
deriving
(Entity)
via (GenericEntity '[]) E
deriving anyclass (FromRow)
-- ```haskell
-- insertEntity :: (MonadIO m)
-- => E
-- -> DBT m ()
-- ```
getEntity
:: (MonadError EntityError m, MonadIO m)
=> Int
-> DBT m E
getEntity key = do
result <- selectById (Only key)
case result of
Just e -> pure e
Nothing -> lift $ throwError EntityNotFound
-- ↑ Here, we convert a valid database response into a more precise
-- business logic component.
-- [MonadError]: https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Except.html#t:MonadError