module Ganeti.BasicTypes
( GenericResult(..)
, genericResult
, Result
, ResultT(..)
, mkResultT
, withError
, withErrorT
, resultT
, toErrorStr
, Error(..)
, isOk
, isBad
, justOk
, justBad
, eitherToResult
, annotateResult
, annotateError
, failError
, catchErrorT
, handleErrorT
, iterateOk
, select
, runListHead
, LookupResult(..)
, MatchPriority(..)
, lookupName
, goodLookupResult
, goodMatchPriority
, prefixMatch
, compareNameComponent
, ListSet(..)
, emptyListSet
) where
import Control.Applicative
import Control.Monad
import Control.Monad.Error.Class
import Control.Monad.Trans
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)}
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 (Monad f) => Functor (ResultT a f) where
fmap f = ResultT . liftM (fmap f) . runResultT
instance (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 . Bad
catchError = catchErrorT
instance MonadTrans (ResultT a) where
lift = ResultT . liftM Ok
instance (MonadIO m, Error a) => MonadIO (ResultT a m) where
liftIO = lift . liftIO
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 (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
resultT :: Monad m => GenericResult a b -> ResultT a m b
resultT = ResultT . return
toErrorStr :: (MonadError e m, Error e) => Result a -> m a
toErrorStr = withError strMsg
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
annotateResult :: String -> Result a -> Result a
annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
annotateResult _ v = v
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
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)
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