{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE DeriveGeneric #-} module Toxiproxy ( getVersion , postReset , getProxies , createProxy , getProxy , postPopulate , updateProxy , deleteProxy , getToxics , createToxic , getToxic , updateToxic , deleteToxic , Proxy(..) , Toxic(..) , Populate(..) , toxiproxyUrl , withDisabled , withToxic , withProxy , run , Version ) where import Servant.API import Servant.Client import qualified Data.Proxy as Proxy import Data.Text (Text) import Data.List (stripPrefix) import Data.Char (toLower) import GHC.Generics import Data.Aeson (FromJSON, parseJSON, fieldLabelModifier, defaultOptions, genericParseJSON, ToJSON, genericToJSON, toJSON) import Data.Map.Strict (Map) import Network.HTTP.Client (newManager, defaultManagerSettings) import Control.Exception (bracket) import Control.Monad (void) type ToxiproxyAPI = "version" :> Get '[PlainText] Version :<|> "reset" :> Post '[] NoContent :<|> "proxies" :> Get '[JSON] (Map Text Proxy) :<|> "proxies" :> ReqBody '[JSON] Proxy :> Post '[JSON] Proxy :<|> "proxies" :> Capture "name" Text :> Get '[JSON] Proxy :<|> "populate" :> ReqBody '[JSON] [Proxy] :> Post '[JSON] Populate :<|> "proxies" :> Capture "name" Text :> ReqBody '[JSON] Proxy :> Post '[JSON] Proxy :<|> "proxies" :> Capture "name" Text :> Delete '[] NoContent :<|> "proxies" :> Capture "name" Text :> "toxics" :> Get '[JSON] [Toxic] :<|> "proxies" :> Capture "name" Text :> "toxics" :> ReqBody '[JSON] Toxic :> Post '[JSON] Toxic :<|> "proxies" :> Capture "name" Text :> "toxics" :> Capture "name" Text :> Get '[JSON] Toxic :<|> "proxies" :> Capture "name" Text :> "toxics" :> Capture "name" Text :> ReqBody '[JSON] Toxic :> Get '[JSON] Toxic :<|> "proxies" :> Capture "name" Text :> "toxics" :> Capture "name" Text :> Delete '[JSON] NoContent type Version = Text data Proxy = Proxy { proxyName :: Text , proxyListen :: Text , proxyUpstream :: Text , proxyEnabled :: Bool , proxyToxics :: [Toxic] } deriving (Show, Eq, Generic) instance FromJSON Proxy where parseJSON = genericParseJSON $ defaultOptions { fieldLabelModifier = stripPrefixJSON "proxy" } instance ToJSON Proxy where toJSON = genericToJSON $ defaultOptions { fieldLabelModifier = stripPrefixJSON "proxy" } data Toxic = Toxic { toxicName :: Text , toxicType :: Text , toxicStream :: Text , toxicToxicity :: Float , toxicAttributes :: Map Text Int } deriving (Show, Eq, Generic) instance FromJSON Toxic where parseJSON = genericParseJSON $ defaultOptions { fieldLabelModifier = stripPrefixJSON "toxic" } instance ToJSON Toxic where toJSON = genericToJSON $ defaultOptions { fieldLabelModifier = stripPrefixJSON "toxic" } newtype Populate = Populate { populateProxies :: [Proxy] } deriving (Show, Eq, Generic) instance FromJSON Populate where parseJSON = genericParseJSON $ defaultOptions { fieldLabelModifier = stripPrefixJSON "populate" } stripPrefixJSON :: String -> String -> String stripPrefixJSON prefix str = case stripPrefix prefix str of Nothing -> str Just (first : rest) -> toLower first : rest toxiproxyAPI :: Proxy.Proxy ToxiproxyAPI toxiproxyAPI = Proxy.Proxy getVersion :: ClientM Version postReset :: ClientM NoContent getProxies :: ClientM (Map Text Proxy) createProxy :: Proxy -> ClientM Proxy getProxy :: Text -> ClientM Proxy postPopulate :: [Proxy] -> ClientM Populate updateProxy :: Text -> Proxy -> ClientM Proxy deleteProxy :: Text -> ClientM NoContent getToxics :: Text -> ClientM [Toxic] createToxic :: Text -> Toxic -> ClientM Toxic getToxic :: Text -> Text -> ClientM Toxic updateToxic :: Text -> Text -> Toxic -> ClientM Toxic deleteToxic :: Text -> Text -> ClientM NoContent (getVersion :<|> postReset :<|> getProxies :<|> createProxy :<|> getProxy :<|> postPopulate :<|> updateProxy :<|> deleteProxy :<|> getToxics :<|> createToxic :<|> getToxic :<|> updateToxic :<|> deleteToxic) = client toxiproxyAPI toxiproxyUrl :: BaseUrl toxiproxyUrl = BaseUrl Http "127.0.0.1" 8474 "" run :: ClientM a -> IO (Either ServantError a) run f = do manager <- newManager defaultManagerSettings runClientM f (ClientEnv manager toxiproxyUrl) withDisabled :: Proxy -> IO a -> IO a withDisabled proxy f = bracket disable enable $ const f where enable = const . run $ updateProxy (proxyName proxy) proxy disable = void . run $ updateProxy (proxyName proxy) disabledProxy disabledProxy = proxy { proxyEnabled = False } withToxic :: Proxy -> Toxic -> IO a -> IO a withToxic proxy toxic f = bracket enable disable $ const f where enable = void . run $ createToxic (proxyName proxy) toxic disable = const . run $ deleteToxic (proxyName proxy) (toxicName toxic) withProxy :: Proxy -> (Proxy -> IO a) -> IO a withProxy proxy = bracket create delete where create = run (createProxy proxy) >> return proxy delete = const . run $ deleteProxy (proxyName proxy)