module Ganeti.BasicTypes
( GenericResult(..)
, genericResult
, Result
, ResultT(..)
, mkResultT
, withError
, withErrorT
, toError
, toErrorBase
, toErrorStr
, tryError
, Error(..)
, MonadIO(..)
, 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
) where
#define MIN_VERSION_monad_control(maj,min,rev) \
(((maj)<MONAD_CONTROL_MAJOR)|| \
(((maj)==MONAD_CONTROL_MAJOR)&&((min)<=MONAD_CONTROL_MINOR))|| \
(((maj)==MONAD_CONTROL_MAJOR)&&((min)==MONAD_CONTROL_MINOR)&& \
((rev)<=MONAD_CONTROL_REV)))
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
import Data.Maybe
import Data.Monoid
import Data.Set (Set)
import qualified Data.Set as Set (empty)
import Text.JSON (JSON)
import qualified Text.JSON as JSON (readJSON, showJSON)
data GenericResult a b
= Bad a
| Ok b
deriving (Show, Eq)
genericResult :: (a -> c) -> (b -> c) -> GenericResult a b -> c
genericResult f _ (Bad a) = f a
genericResult _ g (Ok b) = g b
type Result = GenericResult String
instance (Error a) => Monad (GenericResult a) where
(>>=) (Bad x) _ = Bad x
(>>=) (Ok x) fn = fn x
return = Ok
fail = Bad . strMsg
instance Functor (GenericResult a) where
fmap _ (Bad msg) = Bad msg
fmap fn (Ok val) = Ok (fn val)
instance (Error a, Monoid a) => MonadPlus (GenericResult a) where
mzero = Bad $ strMsg "zero Result when used as MonadPlus"
(Bad x) `mplus` (Bad y) = Bad (x `mappend` strMsg "; " `mappend` y)
(Bad _) `mplus` x = x
x@(Ok _) `mplus` _ = x
instance (Error a) => MonadError a (GenericResult a) where
throwError = Bad
catchError x h = genericResult h (const x) x
instance Applicative (GenericResult a) where
pure = Ok
(Bad f) <*> _ = Bad f
_ <*> (Bad x) = Bad x
(Ok f) <*> (Ok x) = Ok $ f x
instance (Error a, Monoid a) => Alternative (GenericResult a) where
empty = mzero
(<|>) = mplus
newtype ResultT a m b = ResultT {runResultT :: m (GenericResult a b)}
deriving (Functor)
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
instance (Applicative m, Monad m, Error a) => Applicative (ResultT a m) where
pure = return
(<*>) = ap
instance (Monad m, Error a) => Monad (ResultT a m) where
fail err = ResultT (return . Bad $ strMsg err)
return = lift . return
(>>=) = flip (elimResultT throwError)
instance (Monad m, Error a) => MonadError a (ResultT a m) where
throwError = ResultT . return . Bad
catchError = catchErrorT
instance MonadTrans (ResultT a) where
lift = ResultT . liftM Ok
instance (MonadIO m, Error 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, Error a) => MonadBase IO (ResultT a m) where
liftBase = ResultT . liftBase
. liftM (either (failError . show) return)
. (try :: IO a -> IO (Either IOError a))
instance (Error a) => MonadTransControl (ResultT a) where
#if MIN_VERSION_monad_control(1,0,0)
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
instance (Error a, MonadBaseControl IO m)
=> MonadBaseControl IO (ResultT a m) where
#if MIN_VERSION_monad_control(1,0,0)
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
instance (Monad m, Error a, Monoid a) => MonadPlus (ResultT a m) where
mzero = ResultT $ return mzero
mplus x y = elimResultT combine return x
where combine x' = ResultT $ liftM (mplus (Bad x')) (runResultT y)
instance (Alternative m, Monad m, Error a, Monoid a)
=> Alternative (ResultT a m) where
empty = mzero
(<|>) = mplus
withError :: (MonadError e m) => (e' -> e) -> GenericResult e' a -> m a
withError f = genericResult (throwError . f) return
withErrorT :: (Monad m, Error e)
=> (e' -> e) -> ResultT e' m a -> ResultT e m a
withErrorT f = ResultT . liftM (withError f) . runResultT
toError :: (MonadError e m) => GenericResult e a -> m a
toError = genericResult throwError return
toErrorBase :: (MonadBase b m, MonadError e m) => ResultT e b a -> m a
toErrorBase = (toError =<<) . liftBase . runResultT
toErrorStr :: (MonadError e m, Error e) => Result a -> m a
toErrorStr = withError strMsg
tryError :: (MonadError e m) => m a -> m (Either e a)
tryError = flip catchError (return . Left) . liftM Right
mkResultT :: (Monad m, Error e) => m (Result a) -> ResultT e m a
mkResultT = ResultT . liftM toErrorStr
isOk :: GenericResult a b -> Bool
isOk (Ok _) = True
isOk _ = False
isBad :: GenericResult a b -> Bool
isBad = not . isOk
justOk :: [GenericResult a b] -> [b]
justOk = mapMaybe (genericResult (const Nothing) Just)
justBad :: [GenericResult a b] -> [a]
justBad = mapMaybe (genericResult Just (const Nothing))
eitherToResult :: Either a b -> GenericResult a b
eitherToResult (Left s) = Bad s
eitherToResult (Right v) = Ok v
isLeft :: Either a b -> Bool
isLeft (Left _) = True
isLeft _ = False
isRight :: Either a b -> Bool
isRight = not . isLeft
annotateResult :: (MonadError e m, Error e) => String -> Result a -> m a
annotateResult owner = toErrorStr . annotateError owner
annotateError :: (MonadError e m, Error e, Monoid e) => String -> m a -> m a
annotateError owner =
flip catchError (throwError . mappend (strMsg $ owner ++ ": "))
failError :: (MonadError e m, Error e) => String -> m a
failError = throwError . strMsg
handleErrorT :: (Monad m, Error e)
=> (e' -> ResultT e m a) -> ResultT e' m a -> ResultT e m a
handleErrorT handler = elimResultT handler return
catchErrorT :: (Monad m, Error e)
=> ResultT e' m a -> (e' -> ResultT e m a) -> ResultT e m a
catchErrorT = flip handleErrorT
orElse :: (MonadError e m) => m a -> m a -> m a
orElse x y = catchError x (const y)
iterateOk :: (a -> GenericResult b a) -> a -> [a]
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
select :: a
-> [(Bool, a)]
-> a
select def = maybe def snd . find fst
runListHead :: a -> (b -> a) -> [b] -> a
runListHead a f = maybe a f . listToMaybe
data MatchPriority = ExactMatch
| MultipleMatch
| PartialMatch
| FailMatch
deriving (Show, Enum, Eq, Ord)
data LookupResult = LookupResult
{ lrMatchPriority :: MatchPriority
, lrContent :: String
} deriving (Show)
instance Eq LookupResult where
(==) = (==) `on` lrMatchPriority
instance Ord LookupResult where
compare = compare `on` lrMatchPriority
prefixMatch :: String
-> String
-> Bool
prefixMatch = isPrefixOf . (++ ".")
goodMatchPriority :: MatchPriority -> Bool
goodMatchPriority ExactMatch = True
goodMatchPriority PartialMatch = True
goodMatchPriority _ = False
goodLookupResult :: LookupResult -> Bool
goodLookupResult = goodMatchPriority . lrMatchPriority
compareNameComponent :: String
-> String
-> LookupResult
compareNameComponent cnl lkp =
select (LookupResult FailMatch lkp)
[ (cnl == lkp , LookupResult ExactMatch cnl)
, (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
]
chooseLookupResult :: String
-> String
-> LookupResult
-> LookupResult
chooseLookupResult lkp cstr old =
select (min new old)
[ (lrMatchPriority new == ExactMatch, new)
, (partial2, LookupResult MultipleMatch lkp)
] where new = compareNameComponent cstr lkp
partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
lookupName :: [String]
-> String
-> LookupResult
lookupName l s = foldr (chooseLookupResult s)
(LookupResult FailMatch s) l
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