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
import Control.Applicative
import Control.Exception (try)
import Control.Monad
import Control.Monad.Fail (MonadFail)
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.Set (Set)
import qualified Data.Set as Set (empty)
import Text.JSON (JSON)
import qualified Text.JSON as JSON (readJSON, showJSON)
import qualified Control.Monad.Fail as Fail
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
#if !MIN_VERSION_base(4,13,0)
fail = Bad . strMsg
#endif
instance Functor (GenericResult a) where
fmap _ (Bad msg) = Bad msg
fmap fn (Ok val) = Ok (fn val)
instance (Error a, Monoid a) => Alternative (GenericResult a) where
empty = Bad $ strMsg "zero Result when used as empty"
(Bad x) <|> (Bad y) = Bad (x `mappend` strMsg "; " `mappend` y)
(Bad _) <|> x = x
x@(Ok _) <|> _ = x
instance (Error a, Monoid a) => MonadPlus (GenericResult a) where
mzero = empty
mplus = (<|>)
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) => Fail.MonadFail (GenericResult a) where
fail = Bad . strMsg
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
return = lift . return
(>>=) = flip (elimResultT throwError)
#if !MIN_VERSION_base(4,13,0)
fail err = ResultT (return . Bad $ strMsg err)
#endif
instance (Monad m, Error a)=> MonadFail (ResultT a m) where
fail err = ResultT (return . Bad $ strMsg err)
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)
=> Alternative (ResultT a m) where
empty = ResultT $ return mzero
x <|> y = elimResultT combine return x
where combine x' = ResultT $ liftM (mplus (Bad x')) (runResultT y)
instance (Monad m, Error a, Monoid a)
=> MonadPlus (ResultT a m) where
mzero = empty
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