packages feed

duckdb-simple-0.1.5.0: src/Database/DuckDB/Simple/Ok.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DerivingStrategies #-}

{- |
Module      : Database.DuckDB.Simple.Ok
Description : Lightweight error accumulation for row parsing.

The @Ok@ type mirrors the helper used by @sqlite-simple@: it behaves like an
@Either [SomeException]@ with sensible @Alternative@ semantics that accumulate
failure reasons instead of discarding them. This underpins the row parser
machinery in @Database.DuckDB.Simple.FromRow@.
-}
module Database.DuckDB.Simple.Ok (
    Ok (..),
    ManyErrors (..),
) where

import Control.Applicative (Alternative (..))
import Control.Exception (Exception, SomeException, toException)
import Control.Monad (MonadPlus (..))
import qualified Control.Monad.Fail as Fail

-- | Simple success-or-errors container with error accumulation.
data Ok a
    = Errors [SomeException]
    | Ok !a
    deriving stock (Show)
    deriving stock (Functor)

-- | Two failures are considered equal regardless of their payload.
instance (Eq a) => Eq (Ok a) where
    Errors _ == Errors _ = True
    Ok a == Ok b = a == b
    _ == _ = False

instance Applicative Ok where
    pure = Ok
    Ok f <*> Ok a = Ok (f a)
    Errors es <*> Ok _ = Errors es
    Ok _ <*> Errors es = Errors es
    Errors es1 <*> Errors es2 = Errors (es1 <> es2)

instance Alternative Ok where
    empty = Errors []
    Ok x <|> _ = Ok x
    Errors _ <|> Ok y = Ok y
    Errors xs <|> Errors ys = Errors (xs <> ys)

instance Monad Ok where
    Ok a >>= f = f a
    Errors es >>= _ = Errors es

instance MonadPlus Ok where
    mzero = empty
    mplus = (<|>)

instance Fail.MonadFail Ok where
    fail msg = Errors [toException (userError msg)]

-- | Bundle multiple underlying exceptions into a single throwable value.
newtype ManyErrors = ManyErrors [SomeException]
    deriving stock (Show)

instance Exception ManyErrors