{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-| Module : Web.Lightning.Types.Lightning Description : Main Lightning Types Copyright : (c) Connor Moreside, 2016 License : BSD-3 Maintainer : connor@moresi.de Stability : experimental Portability : POSIX -} module Web.Lightning.Types.Lightning ( -- * Lightning Types Lightning , LightningF(..) , LightningT(..) , ValidatablePlot(..) -- * Lightning Actions , runRoute , sendPlot , streamPlot , sendJSON , receiveRoute , withBaseURL , failWith , liftLightningF ) where -------------------------------------------------------------------------------- import Control.Monad.IO.Class import Control.Monad.Reader import Control.Monad.Trans.Free import Data.Aeson import qualified Data.Text as T import Network.API.Builder hiding (runRoute) import Web.Lightning.Routes (stream) import Web.Lightning.Types.Error import Web.Lightning.Types.Visualization import Web.Lightning.Utilities -------------------------------------------------------------------------------- -- | Allows plot fields to be validated. class ValidatablePlot a where validatePlot :: a -> Either LightningError a -- | Represents an IO Lightning transformer action. type Lightning a = LightningT IO a -- | Represents a URL. type BaseUrl = T.Text -- | Defines the available actions data LightningF m a where FailWith :: APIError LightningError -> LightningF m a ReceiveRoute :: Receivable b => Route -> (b -> a) -> LightningF m a RunRoute :: FromJSON b => Route -> (b -> a) -> LightningF m a SendJSON :: (Receivable b) => Value -> Route -> (b -> a) -> LightningF m a WithBaseURL :: T.Text -> LightningT m b -> (b -> a) -> LightningF m a instance Functor (LightningF m) where fmap _ (FailWith x) = FailWith x fmap f (ReceiveRoute r x) = ReceiveRoute r (fmap f x) fmap f (RunRoute r x) = RunRoute r (fmap f x) fmap f (SendJSON js r x) = SendJSON js r (fmap f x) fmap f (WithBaseURL u a x) = WithBaseURL u a (fmap f x) -- | Defines free monad transformer newtype LightningT m a = LightningT (ReaderT BaseUrl (FreeT (LightningF m) m) a) deriving (Functor, Applicative, Monad, MonadReader T.Text) instance MonadIO m => MonadIO (LightningT m) where liftIO = LightningT . liftIO instance MonadTrans LightningT where lift = LightningT . lift . lift -- | Lifts a 'LightningF' free monad into the ReaderT context. liftLightningF :: (Monad m) => FreeT (LightningF m) m a -> ReaderT T.Text (FreeT (LightningF m) m) a liftLightningF = lift -- | Runs a route action within the free monadic transformer context. runRoute :: (FromJSON a, Monad m) => Route -- ^ Route to run -> LightningT m a -- ^ Monad transformer stack with result. runRoute r = LightningT $ liftF $ RunRoute r id -- | Sends a request to the lightning-viz server to create a visualization. sendPlot :: (ToJSON p, ValidatablePlot p, Receivable a, Monad m) => T.Text -- ^ The plot type -> p -- ^ The plot creation request -> Route -- ^ The plot route. -> LightningT m a -- ^ Monad transformer stack with result. sendPlot t p r = case validatePlot p of Left err -> failWith (APIError err) Right p' -> sendJSON (createPayLoad t $ toJSON p') r -- | Sends a request to either create a brand new streaming plot or -- to append data to an existing streaming plot. streamPlot :: (ToJSON p, ValidatablePlot p, Receivable a, Monad m) => Maybe Visualization -- ^ Visualization to update. If nothing, create -- a new plot. -> T.Text -- ^ Plot type -> p -- ^ Plot payload -> Route -- ^ Route to send plot to. -> LightningT m a -- ^ Monad transformer stack with result. streamPlot viz t p r = case validatePlot p of Left err -> failWith (APIError err) Right _ -> streamOrCreate viz where streamOrCreate (Just viz') = sendJSON (createDataPayLoad $ toJSON p) (stream viz') streamOrCreate Nothing = sendJSON (createPayLoad t $ toJSON p) r -- | Sends a request containing JSON to the specified route. sendJSON :: (Receivable a, Monad m) => Value -- ^ The JSON payload -> Route -- ^ Route to send request to -> LightningT m a -- ^ Monad transformer stack with result. sendJSON j r = LightningT $ liftF $ SendJSON j r id -- | Send and receives a GET request to the specified route. receiveRoute :: (Receivable a, Monad m) => Route -- ^ The route to retrieve data from. -> LightningT m a -- ^ Monad transformer stack with result. receiveRoute r = LightningT $ liftF $ ReceiveRoute r id -- | Replaces the base URL in the stack and run the supplied action afterwards. withBaseURL :: Monad m => T.Text -- ^ The new base URL. -> LightningT m a -- ^ Next action to run. -> LightningT m a -- ^ Monad transformer stack with result. withBaseURL u f = LightningT $ liftF $ WithBaseURL u f id -- | Returns an error message. failWith :: Monad m => APIError LightningError -- ^ The error message to return. -> LightningT m a -- ^ Monad transformer stack with error. failWith = LightningT . liftF . FailWith