{-# LANGUAGE TypeApplications #-} -- | General-purpose utility functions module Serokell.Util.Verify ( VerificationRes (..) -- * Helpers , isVerFailure , isVerSuccess , verResToMonadError -- * Verification , verifyGeneric -- * Prety printing , formatAllErrors , formatFirstError , verResFullF , verResSingleF ) where import Universum import Control.Monad.Except (MonadError, throwError) import Fmt (fmt) import Serokell.Util.Text (listBuilder) import qualified Data.Text.Lazy.Builder as B data VerificationRes = VerSuccess | VerFailure !(NonEmpty 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 a <> VerSuccess = a VerFailure xs <> VerFailure ys = VerFailure $ xs <> ys instance Monoid VerificationRes where mempty = VerSuccess mappend = (<>) -- | 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, Text)] -> VerificationRes verifyGeneric errors = case messages of [] -> VerSuccess (x:xs) -> VerFailure $ x :| xs where messages = map snd . filter (not . fst) $ errors ---------------------------------------------------------------------------- -- Pretty printing ---------------------------------------------------------------------------- -- | Format VerificationRes in a pretty way using all errors messages -- for VerFailure. verResFullF :: VerificationRes -> Text verResFullF VerSuccess = "success" verResFullF (VerFailure errors) = fmt $ buildVerResImpl errors -- | Format VerificationRes in a pretty way using only first message -- for VerFailure. verResSingleF :: VerificationRes -> Text verResSingleF VerSuccess = "success" verResSingleF (VerFailure errors) = fmt $ buildVerResImpl $ one $ head errors buildVerResImpl :: NonEmpty Text -> B.Builder buildVerResImpl errors = "failure: " <> listBuilder @Text @Text @Text "[" "; " "]" errors -- These two functions can have more general type. -- | Pretty printer for errors from VerFailure, all errors are printed. formatAllErrors :: NonEmpty Text -> Text formatAllErrors = verResFullF . VerFailure -- | Pretty printer for errors from VerFailure, only first error is printed. formatFirstError :: NonEmpty Text -> Text formatFirstError = verResSingleF . VerFailure ---------------------------------------------------------------------------- -- Conversion to MonadError (including Either) ---------------------------------------------------------------------------- verResToMonadError :: MonadError e m => (NonEmpty Text -> e) -> VerificationRes -> m () verResToMonadError _ VerSuccess = pure () verResToMonadError f (VerFailure errors) = throwError $ f errors