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