{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} module Propellor.Types.ResultCheck ( UncheckedProperty, unchecked, checkResult, check, Checkable, assume, ) where import Propellor.Types import Propellor.Exception import Utility.Monad import Data.Monoid import Prelude -- | This is a `Property` but its `Result` is not accurate; in particular -- it may return `NoChange` despite having made a change. -- -- However, when it returns `MadeChange`, it really did make a change, -- and `FailedChange` is still an error. data UncheckedProperty i = UncheckedProperty (Property i) instance TightenTargets UncheckedProperty where tightenTargets (UncheckedProperty p) = UncheckedProperty (tightenTargets p) -- | Use to indicate that a Property is unchecked. unchecked :: Property i -> UncheckedProperty i unchecked = UncheckedProperty -- | Checks the result of a property. Mostly used to convert a -- `UncheckedProperty` to a `Property`, but can also be used to further -- check a `Property`. checkResult :: (Checkable p i, LiftPropellor m) => m a -- ^ Run before ensuring the property. -> (a -> m Result) -- ^ Run after ensuring the property. Return `MadeChange` if a -- change was detected, or `NoChange` if no change was detected. -> p i -> Property i checkResult precheck postcheck p = adjustPropertySatisfy (checkedProp p) $ \satisfy -> do a <- liftPropellor precheck r <- catchPropellor satisfy -- Always run postcheck, even if the result is already MadeChange, -- as it may need to clean up after precheck. r' <- liftPropellor $ postcheck a return (r <> r') -- | Makes a `Property` or an `UncheckedProperty` only run -- when a test succeeds. check :: (Checkable p i, LiftPropellor m) => m Bool -> p i -> Property i check test p = adjustPropertySatisfy (preCheckedProp p) $ \satisfy -> ifM (liftPropellor test) ( satisfy , return NoChange ) class Checkable p i where checkedProp :: p i -> Property i preCheckedProp :: p i -> Property i instance Checkable Property i where checkedProp = id preCheckedProp = id instance Checkable UncheckedProperty i where checkedProp (UncheckedProperty p) = p -- Since it was pre-checked that the property needed to be run, -- if the property succeeded, we can assume it made a change. preCheckedProp (UncheckedProperty p) = p `assume` MadeChange -- | Sometimes it's not practical to test if a property made a change. -- In such a case, it's often fine to say: -- -- > someprop `assume` MadeChange -- -- However, beware assuming `NoChange`, as that will make combinators -- like `onChange` not work. assume :: Checkable p i => p i -> Result -> Property i assume p result = adjustPropertySatisfy (checkedProp p) $ \satisfy -> do r <- satisfy return (r <> result)