serokell-util-0.1.3.3: src/Serokell/Util/Verify.hs
{-# LANGUAGE TypeApplications #-}
-- | General-purpose utility functions
module Serokell.Util.Verify
( VerificationRes (..)
-- * Helpers
, isVerFailure
, isVerSuccess
, verResToMonadError
-- * Verification
, verifyGeneric
-- * Prety printing
, buildVerResFull
, buildVerResSingle
, formatAllErrors
, formatFirstError
, verResFullF
, verResSingleF
) where
import Control.Monad.Except (MonadError, throwError)
import Data.Semigroup (Semigroup)
import qualified Data.Semigroup as Semigroup
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy.Builder as B
import Formatting (Format, later, sformat)
import Serokell.Util.Text (listBuilder)
data VerificationRes
= VerSuccess
| VerFailure ![T.Text]
deriving (Eq, Show)
isVerSuccess :: VerificationRes -> Bool
isVerSuccess VerSuccess = True
isVerSuccess _ = False
isVerFailure :: VerificationRes -> Bool
isVerFailure (VerFailure _) = True
isVerFailure _ = False
instance Semigroup VerificationRes where
VerSuccess <> a = a
VerFailure xs <> a =
VerFailure $
xs ++
case a of
VerSuccess -> []
VerFailure ys -> ys
instance Monoid VerificationRes where
mempty = VerSuccess
mappend = (Semigroup.<>)
-- | This function takes list of (predicate, message) pairs and checks
-- each predicate. If predicate is False it's considered an error.
-- If there is at least one error this function returns VerFailure,
-- otherwise VerSuccess is returned. It's useful to verify some data
-- before using it.
-- Example usage: `verifyGeneric [(checkA, "A is bad"), (checkB, "B is bad")]`
verifyGeneric :: [(Bool, T.Text)] -> VerificationRes
verifyGeneric errors
| null messages = VerSuccess
| otherwise = VerFailure messages
where
messages = map snd . filter (not . fst) $ errors
----------------------------------------------------------------------------
-- Pretty printing
----------------------------------------------------------------------------
-- | Format VerificationRes in a pretty way using all errors messages
-- for VerFailure.
buildVerResFull :: VerificationRes -> B.Builder
buildVerResFull VerSuccess = buildVerResImpl Nothing
buildVerResFull (VerFailure errors) = buildVerResImpl $ Just errors
-- | Format VerificationRes in a pretty way using only first message
-- for VerFailure.
buildVerResSingle :: VerificationRes -> B.Builder
buildVerResSingle VerSuccess = buildVerResImpl Nothing
-- [SU-1] Use NonEmpty instead of unsafe head.
buildVerResSingle (VerFailure errors) = buildVerResImpl $ Just $ [head errors]
buildVerResImpl :: Maybe [T.Text] -> B.Builder
buildVerResImpl Nothing = "success"
buildVerResImpl (Just errors) =
"failure: " `mappend` listBuilder @Text @Text @Text "[" "; " "]" errors
-- | Formatter based on buildVerResFull.
verResFullF :: Format r (VerificationRes -> r)
verResFullF = later buildVerResFull
-- | Formatter based on buildVerResSingle.
verResSingleF :: Format r (VerificationRes -> r)
verResSingleF = later buildVerResSingle
-- These two functions can have more general type.
-- | Pretty printer for errors from VerFailure, all errors are printed.
formatAllErrors :: [Text] -> Text
formatAllErrors = sformat verResFullF . VerFailure
-- | Pretty printer for errors from VerFailure, only first error is printed.
formatFirstError :: [Text] -> Text
formatFirstError = sformat verResSingleF . VerFailure
----------------------------------------------------------------------------
-- Conversion to MonadError (including Either)
----------------------------------------------------------------------------
verResToMonadError
:: MonadError e m
=> ([Text] -> e) -> VerificationRes -> m ()
verResToMonadError _ VerSuccess = pure ()
verResToMonadError f (VerFailure errors) = throwError $ f errors