{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE Rank2Types #-} -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore.Internal.Operation -- Copyright : (C) 2015 Yorick Laupa -- License : (see the file LICENSE) -- -- Maintainer : Yorick Laupa -- Stability : provisional -- Portability : non-portable -- -------------------------------------------------------------------------------- module Database.EventStore.Internal.Operation where -------------------------------------------------------------------------------- import ClassyPrelude import Data.ProtocolBuffers import Data.UUID -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types -------------------------------------------------------------------------------- -- | Operation result sent by the server. data OpResult = OP_SUCCESS | OP_PREPARE_TIMEOUT | OP_COMMIT_TIMEOUT | OP_FORWARD_TIMEOUT | OP_WRONG_EXPECTED_VERSION | OP_STREAM_DELETED | OP_INVALID_TRANSACTION | OP_ACCESS_DENIED deriving (Eq, Enum, Show) -------------------------------------------------------------------------------- -- | Operation exception that can occurs on an operation response. data OperationError = WrongExpectedVersion Text ExpectedVersion -- ^ Stream and Expected Version | StreamDeleted Text -- ^ Stream | InvalidTransaction | AccessDenied StreamName -- ^ Stream | InvalidServerResponse Command Command -- ^ Expected, Found | ProtobufDecodingError String | ServerError (Maybe Text) -- ^ Reason | InvalidOperation Text -- ^ Invalid operation state. If happens, it's a driver bug. | Aborted -- ^ Occurs when the user asked to close the connection or if the -- connection can't reconnect anymore. deriving (Show, Typeable) -------------------------------------------------------------------------------- instance Exception OperationError -------------------------------------------------------------------------------- -- | Main operation state machine instruction. data SM o a = Return a -- ^ Lifts a pure value into the intruction tree. Also marks the end of -- an instruction tree. | Yield o (SM o a) -- ^ Emits an operation return value. | FreshId (UUID -> SM o a) -- ^ Asks for an unused 'UUID'. | forall rq rp. (Encode rq, Decode rp) => SendPkg Command Command rq (rp -> SM o a) -- ^ Send a request message given a command and an expected command. -- response. It also carries a callback to call when response comes in. | Failure (Maybe OperationError) -- ^ Ends the instruction interpretation. If holds Nothing, the -- interpretation should resume from the beginning. Otherwise it ends -- by indicating what went wrong. -------------------------------------------------------------------------------- instance Functor (SM o) where fmap f (Return a) = Return (f a) fmap f (Yield o n) = Yield o (fmap f n) fmap f (FreshId k) = FreshId (fmap f . k) fmap f (SendPkg ci co p k) = SendPkg ci co p (fmap f . k) fmap _ (Failure e) = Failure e -------------------------------------------------------------------------------- instance Applicative (SM o) where pure = return (<*>) = ap -------------------------------------------------------------------------------- instance Monad (SM o) where return = Return Return a >>= f = f a Yield o n >>= f = Yield o (n >>= f) FreshId k >>= f = FreshId ((f =<<) . k) SendPkg ci co p k >>= f = SendPkg ci co p ((f =<<) . k) Failure e >>= _ = Failure e -------------------------------------------------------------------------------- -- | Asks for a unused 'UUID'. freshId :: SM o UUID freshId = FreshId Return -------------------------------------------------------------------------------- -- | Raises an 'OperationError'. failure :: OperationError -> SM o a failure e = Failure $ Just e -------------------------------------------------------------------------------- -- | Asks to resume the interpretation from the beginning. retry :: SM o a retry = Failure Nothing -------------------------------------------------------------------------------- -- | Sends a request to the server given a command request and response. It -- returns the expected deserialized message. send :: (Encode rq, Decode rp) => Command -> Command -> rq -> SM o rp send ci co rq = SendPkg ci co rq Return -------------------------------------------------------------------------------- -- | Emits operation return value. yield :: o -> SM o () yield o = Yield o (Return ()) -------------------------------------------------------------------------------- -- | Replaces every emitted value, via 'yield' function by calling the given -- callback. foreach :: SM a x -> (a -> SM b x) -> SM b x foreach start k = go start where go (Return x) = Return x go (Yield a n) = k a >> go n go (FreshId ki) = FreshId (go . ki) go (SendPkg ci co p kp) = SendPkg ci co p (go . kp) go (Failure e) = Failure e -------------------------------------------------------------------------------- -- | Maps every emitted value, via 'yield', using given function. mapOp :: (a -> b) -> SM a () -> SM b () mapOp k sm = foreach sm (yield . k) -------------------------------------------------------------------------------- -- | An operation is just a 'SM' tree. type Operation a = SM a () -------------------------------------------------------------------------------- -- | Raises 'WrongExpectedVersion' exception. wrongVersion :: Text -> ExpectedVersion -> SM o a wrongVersion stream ver = failure (WrongExpectedVersion stream ver) -------------------------------------------------------------------------------- -- | Raises 'StreamDeleted' exception. streamDeleted :: Text -> SM o a streamDeleted stream = failure (StreamDeleted stream) -------------------------------------------------------------------------------- -- | Raises 'InvalidTransaction' exception. invalidTransaction :: SM o a invalidTransaction = failure InvalidTransaction -------------------------------------------------------------------------------- -- | Raises 'AccessDenied' exception. accessDenied :: StreamName -> SM o a accessDenied = failure . AccessDenied -------------------------------------------------------------------------------- -- | Raises 'ProtobufDecodingError' exception. protobufDecodingError :: String -> SM o a protobufDecodingError = failure . ProtobufDecodingError -------------------------------------------------------------------------------- -- | Raises 'ServerError' exception. serverError :: Maybe Text -> SM o a serverError = failure . ServerError -------------------------------------------------------------------------------- -- | Raises 'InvalidServerResponse' exception. invalidServerResponse :: Command -> Command -> SM o a invalidServerResponse expe got = failure $ InvalidServerResponse expe got