{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE CPP #-} {- Copyright (C) 2009, 2010, 2011, 2012, 2015 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -} module Ganeti.BasicTypes ( GenericResult(..) , genericResult , Result , ResultT(..) , mkResultT , mkResultT' , withError , withErrorT , toError , toErrorBase , toErrorStr , tryError , Error(..) -- re-export from Control.Monad.Error , MonadIO(..) -- re-export from Control.Monad.IO.Class , FromString(..) , isOk , isBad , justOk , justBad , eitherToResult , isLeft , isRight , annotateResult , annotateError , failError , catchErrorT , handleErrorT , orElse , iterateOk , select , runListHead , LookupResult(..) , MatchPriority(..) , lookupName , goodLookupResult , goodMatchPriority , prefixMatch , compareNameComponent , ListSet(..) , emptyListSet , Down(..) ) where import Prelude () import Ganeti.Prelude import Control.Applicative import Control.Exception (try) import Control.Monad import Control.Monad.Base import Control.Monad.Error.Class import Control.Monad.Trans import Control.Monad.Trans.Control import Data.Function import Data.List (find, isPrefixOf) import Data.Maybe import Data.Set (Set) import qualified Data.Set as Set (empty) import Text.JSON (JSON) import qualified Text.JSON as JSON (readJSON, showJSON) #if MIN_VERSION_base(4,6,0) import Data.Ord #endif -- Remove after we require >= 1.8.58 -- See: https://github.com/ndmitchell/hlint/issues/24 {-# ANN module "HLint: ignore Unused LANGUAGE pragma" #-} -- | Generic monad for our error handling mechanisms. data GenericResult a b = Bad a | Ok b deriving (Show, Eq) -- | Sum type structure of GenericResult. genericResult :: (a -> c) -> (b -> c) -> GenericResult a b -> c genericResult f _ (Bad a) = f a genericResult _ g (Ok b) = g b {-# INLINE genericResult #-} -- | Type alias for a string Result. type Result = GenericResult String -- | Type class for things that can be built from strings. class FromString a where mkFromString :: String -> a -- | Trivial 'String' instance; requires FlexibleInstances extension -- though. instance FromString [Char] where mkFromString = id instance FromString IOError where mkFromString = userError -- | 'Monad' instance for 'GenericResult'. instance (FromString a) => Monad (GenericResult a) where (>>=) (Bad x) _ = Bad x (>>=) (Ok x) fn = fn x return = Ok fail = Bad . mkFromString instance Functor (GenericResult a) where fmap _ (Bad msg) = Bad msg fmap fn (Ok val) = Ok (fn val) instance (FromString a, Monoid a) => Alternative (GenericResult a) where empty = Bad $ mkFromString "zero Result when used as empty" -- for mplus, when we 'add' two Bad values, we concatenate their -- error descriptions (Bad x) <|> (Bad y) = Bad (x `mappend` mkFromString "; " `mappend` y) (Bad _) <|> x = x x@(Ok _) <|> _ = x instance (FromString a, Monoid a) => MonadPlus (GenericResult a) where mzero = empty mplus = (<|>) instance (FromString a) => MonadError a (GenericResult a) where throwError = Bad {-# INLINE throwError #-} catchError x h = genericResult h (const x) x {-# INLINE catchError #-} instance Applicative (GenericResult a) where pure = Ok (Bad f) <*> _ = Bad f _ <*> (Bad x) = Bad x (Ok f) <*> (Ok x) = Ok $ f x -- | This is a monad transformation for Result. It's implementation is -- based on the implementations of MaybeT and ErrorT. -- -- 'ResultT' is very similar to @ErrorT@, but with one subtle difference: -- If 'mplus' combines two failing operations, errors of both of them -- are combined. newtype ResultT a m b = ResultT {runResultT :: m (GenericResult a b)} -- | Eliminates a 'ResultT' value given appropriate continuations elimResultT :: (Monad m) => (a -> ResultT a' m b') -> (b -> ResultT a' m b') -> ResultT a m b -> ResultT a' m b' elimResultT l r = ResultT . (runResultT . result <=< runResultT) where result (Ok x) = r x result (Bad e) = l e {-# INLINE elimResultT #-} instance (Monad m) => Functor (ResultT a m) where fmap f = ResultT . liftM (fmap f) . runResultT instance (Monad m, FromString a) => Applicative (ResultT a m) where pure = return (<*>) = ap instance (Monad m, FromString a) => Monad (ResultT a m) where fail err = ResultT (return . Bad $ mkFromString err) return = lift . return (>>=) = flip (elimResultT throwError) instance (Monad m, FromString a) => MonadError a (ResultT a m) where throwError = ResultT . return . Bad catchError = catchErrorT instance MonadTrans (ResultT a) where lift = ResultT . liftM Ok -- | The instance catches any 'IOError' using 'try' and converts it into an -- error message using 'mkFromString'. -- -- This way, monadic code within 'ResultT' that uses solely 'liftIO' to -- include 'IO' actions ensures that all IO exceptions are handled. -- -- Other exceptions (see instances of 'Exception') are not currently handled. -- This might be revised in the future. instance (MonadIO m, FromString a) => MonadIO (ResultT a m) where liftIO = ResultT . liftIO . liftM (either (failError . show) return) . (try :: IO a -> IO (Either IOError a)) instance (MonadBase IO m, FromString a) => MonadBase IO (ResultT a m) where liftBase = ResultT . liftBase . liftM (either (failError . show) return) . (try :: IO a -> IO (Either IOError a)) instance (FromString a) => MonadTransControl (ResultT a) where #if MIN_VERSION_monad_control(1,0,0) -- Needs Undecidable instances type StT (ResultT a) b = GenericResult a b liftWith f = ResultT . liftM return $ f runResultT restoreT = ResultT #else newtype StT (ResultT a) b = StResultT { runStResultT :: GenericResult a b } liftWith f = ResultT . liftM return $ f (liftM StResultT . runResultT) restoreT = ResultT . liftM runStResultT #endif {-# INLINE liftWith #-} {-# INLINE restoreT #-} instance (FromString a, MonadBaseControl IO m) => MonadBaseControl IO (ResultT a m) where #if MIN_VERSION_monad_control(1,0,0) -- Needs Undecidable instances type StM (ResultT a m) b = ComposeSt (ResultT a) m b liftBaseWith = defaultLiftBaseWith restoreM = defaultRestoreM #else newtype StM (ResultT a m) b = StMResultT { runStMResultT :: ComposeSt (ResultT a) m b } liftBaseWith = defaultLiftBaseWith StMResultT restoreM = defaultRestoreM runStMResultT #endif {-# INLINE liftBaseWith #-} {-# INLINE restoreM #-} instance (Monad m, FromString a, Monoid a) => Alternative (ResultT a m) where empty = ResultT $ return mzero -- Ensure that 'y' isn't run if 'x' contains a value. This makes it a bit -- more complicated than 'mplus' of 'GenericResult'. x <|> y = elimResultT combine return x where combine x' = ResultT $ liftM (mplus (Bad x')) (runResultT y) instance (Monad m, FromString a, Monoid a) => MonadPlus (ResultT a m) where mzero = empty mplus = (<|>) -- | Changes the error message of a result value, if present. -- Note that since 'GenericResult' is also a 'MonadError', this function -- is a generalization of -- @(Error e') => (e' -> e) -> GenericResult e' a -> GenericResult e a@ withError :: (MonadError e m) => (e' -> e) -> GenericResult e' a -> m a withError f = genericResult (throwError . f) return -- | Changes the error message of a @ResultT@ value, if present. withErrorT :: (Monad m, FromString e) => (e' -> e) -> ResultT e' m a -> ResultT e m a withErrorT f = ResultT . liftM (withError f) . runResultT -- | Lift a 'Result' value to any 'MonadError'. Since 'ResultT' is itself its -- instance, it's a generalization of -- @Monad m => GenericResult a b -> ResultT a m b@. toError :: (MonadError e m) => GenericResult e a -> m a toError = genericResult throwError return {-# INLINE toError #-} -- | Lift a 'ResultT' value into any 'MonadError' with the same base monad. toErrorBase :: (MonadBase b m, MonadError e m) => ResultT e b a -> m a toErrorBase = (toError =<<) . liftBase . runResultT {-# INLINE toErrorBase #-} -- | An alias for @withError mkFromString@, which is often -- used to lift a pure error to a monad stack. See also 'annotateResult'. toErrorStr :: (MonadError e m, FromString e) => Result a -> m a toErrorStr = withError mkFromString -- | Run a given computation and if an error occurs, return it as `Left` of -- `Either`. -- This is a generalized version of 'try'. tryError :: (MonadError e m) => m a -> m (Either e a) tryError = flip catchError (return . Left) . liftM Right {-# INLINE tryError #-} -- | Converts a monadic result with a 'String' message into -- a 'ResultT' with an arbitrary 'Error'. -- -- Expects that the given action has already taken care of any possible -- errors. In particular, if applied on @IO (Result a)@, any exceptions -- should be handled by the given action. -- -- See also 'toErrorStr'. mkResultT :: (Monad m, FromString e) => m (Result a) -> ResultT e m a mkResultT = ResultT . liftM toErrorStr -- | Generalisation of mkResultT accepting any showable failures. mkResultT' :: (Monad m, FromString e, Show s) => m (GenericResult s a) -> ResultT e m a mkResultT' = mkResultT . liftM (genericResult (Bad . show) Ok) -- | Simple checker for whether a 'GenericResult' is OK. isOk :: GenericResult a b -> Bool isOk (Ok _) = True isOk _ = False -- | Simple checker for whether a 'GenericResult' is a failure. isBad :: GenericResult a b -> Bool isBad = not . isOk -- | Simple filter returning only OK values of GenericResult justOk :: [GenericResult a b] -> [b] justOk = mapMaybe (genericResult (const Nothing) Just) -- | Simple filter returning only Bad values of GenericResult justBad :: [GenericResult a b] -> [a] justBad = mapMaybe (genericResult Just (const Nothing)) -- | Converter from Either to 'GenericResult'. eitherToResult :: Either a b -> GenericResult a b eitherToResult (Left s) = Bad s eitherToResult (Right v) = Ok v -- | Check if an either is Left. Equivalent to isLeft from Data.Either -- version 4.7.0.0 or higher. isLeft :: Either a b -> Bool isLeft (Left _) = True isLeft _ = False -- | Check if an either is Right. Equivalent to isRight from Data.Either -- version 4.7.0.0 or higher. isRight :: Either a b -> Bool isRight = not . isLeft -- | Annotate an error with an ownership information, lifting it to a -- 'MonadError'. Since 'Result' is an instance of 'MonadError' itself, -- it's a generalization of type @String -> Result a -> Result a@. -- See also 'toErrorStr'. annotateResult :: (MonadError e m, FromString e) => String -> Result a -> m a annotateResult owner = toErrorStr . annotateError owner -- | Annotate an error with an ownership information inside a 'MonadError'. -- See also 'annotateResult'. annotateError :: (MonadError e m, FromString e, Monoid e) => String -> m a -> m a annotateError owner = flip catchError (throwError . mappend (mkFromString $ owner ++ ": ")) {-# INLINE annotateError #-} -- | Throws a 'String' message as an error in a 'MonadError'. -- This is a generalization of 'Bad'. -- It's similar to 'fail', but works within a 'MonadError', avoiding the -- unsafe nature of 'fail'. failError :: (MonadError e m, FromString e) => String -> m a failError = throwError . mkFromString -- | A synonym for @flip@ 'catchErrorT'. handleErrorT :: (Monad m, FromString e) => (e' -> ResultT e m a) -> ResultT e' m a -> ResultT e m a handleErrorT handler = elimResultT handler return {-# INLINE handleErrorT #-} -- | Catches an error in a @ResultT@ value. This is similar to 'catchError', -- but in addition allows to change the error type. catchErrorT :: (Monad m, FromString e) => ResultT e' m a -> (e' -> ResultT e m a) -> ResultT e m a catchErrorT = flip handleErrorT {-# INLINE catchErrorT #-} -- | If the first computation fails, run the second one. -- Unlike 'mplus' instance for 'ResultT', this doesn't require -- the 'Monoid' constrait. orElse :: (MonadError e m) => m a -> m a -> m a orElse x y = catchError x (const y) -- | Iterate while Ok. iterateOk :: (a -> GenericResult b a) -> a -> [a] iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a) -- * Misc functionality -- | Return the first result with a True condition, or the default otherwise. select :: a -- ^ default result -> [(Bool, a)] -- ^ list of \"condition, result\" -> a -- ^ first result which has a True condition, or default select def = maybe def snd . find fst -- | Apply a function to the first element of a list, return the default -- value, if the list is empty. This is just a convenient combination of -- maybe and listToMaybe. runListHead :: a -> (b -> a) -> [b] -> a runListHead a f = maybe a f . listToMaybe -- * Lookup of partial names functionality -- | The priority of a match in a lookup result. data MatchPriority = ExactMatch | MultipleMatch | PartialMatch | FailMatch deriving (Show, Enum, Eq, Ord) -- | The result of a name lookup in a list. data LookupResult = LookupResult { lrMatchPriority :: MatchPriority -- ^ The result type -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise , lrContent :: String } deriving (Show) -- | Lookup results have an absolute preference ordering. instance Eq LookupResult where (==) = (==) `on` lrMatchPriority instance Ord LookupResult where compare = compare `on` lrMatchPriority -- | Check for prefix matches in names. -- Implemented in Ganeti core utils.text.MatchNameComponent -- as the regexp r"^%s(\..*)?$" % re.escape(key) prefixMatch :: String -- ^ Lookup -> String -- ^ Full name -> Bool -- ^ Whether there is a prefix match prefixMatch = isPrefixOf . (++ ".") -- | Is the lookup priority a "good" one? goodMatchPriority :: MatchPriority -> Bool goodMatchPriority ExactMatch = True goodMatchPriority PartialMatch = True goodMatchPriority _ = False -- | Is the lookup result an actual match? goodLookupResult :: LookupResult -> Bool goodLookupResult = goodMatchPriority . lrMatchPriority -- | Compares a canonical name and a lookup string. compareNameComponent :: String -- ^ Canonical (target) name -> String -- ^ Partial (lookup) name -> LookupResult -- ^ Result of the lookup compareNameComponent cnl lkp = select (LookupResult FailMatch lkp) [ (cnl == lkp , LookupResult ExactMatch cnl) , (prefixMatch lkp cnl , LookupResult PartialMatch cnl) ] -- | Lookup a string and choose the best result. chooseLookupResult :: String -- ^ Lookup key -> String -- ^ String to compare to the lookup key -> LookupResult -- ^ Previous result -> LookupResult -- ^ New result chooseLookupResult lkp cstr old = -- default: use class order to pick the minimum result select (min new old) -- special cases: -- short circuit if the new result is an exact match [ (lrMatchPriority new == ExactMatch, new) -- if both are partial matches generate a multiple match , (partial2, LookupResult MultipleMatch lkp) ] where new = compareNameComponent cstr lkp partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new] -- | Find the canonical name for a lookup string in a list of names. lookupName :: [String] -- ^ List of keys -> String -- ^ Lookup string -> LookupResult -- ^ Result of the lookup lookupName l s = foldr (chooseLookupResult s) (LookupResult FailMatch s) l -- | Wrapper for a Haskell 'Set' -- -- This type wraps a 'Set' and it is used in the Haskell to Python -- opcode generation to transform a Haskell 'Set' into a Python 'list' -- without duplicate elements. newtype ListSet a = ListSet { unListSet :: Set a } deriving (Eq, Show, Ord) instance (Ord a, JSON a) => JSON (ListSet a) where showJSON = JSON.showJSON . unListSet readJSON = liftM ListSet . JSON.readJSON emptyListSet :: ListSet a emptyListSet = ListSet Set.empty #if MIN_VERSION_base(4,6,0) -- Down already defined in Data.Ord #else -- Copyright : (c) The University of Glasgow 2005 -- License : BSD-style newtype Down a = Down a deriving (Eq, Show, Read) instance Ord a => Ord (Down a) where compare (Down x) (Down y) = y `compare` x {- License text of the above code fragment: The Glasgow Haskell Compiler License Copyright 2004, The University Court of the University of Glasgow. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -} #endif