{- |
Module : Unleash.Internal.HttpClient
Copyright : Copyright © FINN.no AS, Inc. All rights reserved.
License : MIT
Stability : experimental
-}
module Unleash.Internal.HttpClient (
getAllClientFeatures,
register,
sendMetrics,
) where
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson (ToJSON, encode)
import qualified Data.List.NonEmpty as NE
import Data.Map.Strict (Map, fromListWith)
import Data.Proxy (Proxy (..))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Version (showVersion)
import qualified Network.HTTP.Media as M
import Paths_unleash_client_haskell (version)
import Servant.API (Accept (contentTypes), Get, Header, JSON, MimeRender (mimeRender), NoContent, PostNoContent, ReqBody, type (:<|>) (..), type (:>))
import Servant.Client (ClientEnv, ClientError, client, runClientM)
import Unleash.Internal.DomainTypes (Features, fromJsonFeatures, supportedStrategies)
import Unleash.Internal.JsonTypes (FullMetricsBucket (..), FullMetricsPayload (..), FullRegisterPayload (..), MetricsPayload, RegisterPayload, YesAndNoes (..))
import qualified Unleash.Internal.JsonTypes as UJT
type Register = "api" :> "client" :> "register" :> Header "Authorization" Text :> Header "Content-Type" Text :> ReqBody '[CustomJSON] FullRegisterPayload :> PostNoContent
type GetAllClientFeatures = "api" :> "client" :> "features" :> Header "Authorization" Text :> Get '[JSON] UJT.Features
type SendMetrics = "api" :> "client" :> "metrics" :> Header "Authorization" Text :> ReqBody '[CustomJSON] FullMetricsPayload :> PostNoContent
type Api = GetAllClientFeatures :<|> SendMetrics :<|> Register
getAllClientFeatures' :<|> sendMetrics' :<|> register' = client api
api :: Proxy Api
api = Proxy
type ApiKey = Text
data CustomJSON = CustomJSON
-- Remove charset=utf-8 because older versions of Unleash (e.g. 3.17.4) does not recognize it
instance Accept CustomJSON where
contentTypes _ =
"application" M.// "json"
NE.:| ["application" M.// "json"]
instance {-# OVERLAPPABLE #-} ToJSON a => MimeRender CustomJSON a where
mimeRender _ = encode
register :: MonadIO m => ClientEnv -> Maybe ApiKey -> RegisterPayload -> m (Either ClientError NoContent)
register clientEnv apiKey registerPayload = do
let fullRegisterPayload =
FullRegisterPayload
{ appName = registerPayload.appName,
instanceId = registerPayload.instanceId,
sdkVersion = "unleash-client-haskell:" <> (T.pack . showVersion) version,
strategies = supportedStrategies,
started = registerPayload.started,
interval = registerPayload.intervalSeconds * 1000
}
liftIO $ runClientM (register' apiKey (Just "application/json") fullRegisterPayload) clientEnv
getAllClientFeatures :: MonadIO m => ClientEnv -> Maybe ApiKey -> m (Either ClientError Features)
getAllClientFeatures clientEnv apiKey = do
eitherFeatures <- liftIO $ runClientM (getAllClientFeatures' apiKey) clientEnv
pure $ fromJsonFeatures <$> eitherFeatures
sendMetrics :: MonadIO m => ClientEnv -> Maybe ApiKey -> MetricsPayload -> m (Either ClientError NoContent)
sendMetrics clientEnv apiKey metricsPayload = do
liftIO $ runClientM (sendMetrics' apiKey fullMetricsPayload) clientEnv
where
fullMetricsPayload :: FullMetricsPayload
fullMetricsPayload =
FullMetricsPayload
{ appName = metricsPayload.appName,
instanceId = metricsPayload.instanceId,
bucket =
FullMetricsBucket
{ start = metricsPayload.start,
stop = metricsPayload.stop,
toggles = makeMapOfYesAndNoes metricsPayload.toggles
}
}
makeMapOfYesAndNoes :: [(Text, Bool)] -> Map Text YesAndNoes
makeMapOfYesAndNoes tuples = do
let withSingletonLists :: [(Text, [Bool])] = (\(k, v) -> (k, [v])) <$> tuples
let asMap :: (Map Text [Bool]) = fromListWith (++) withSingletonLists
boolsToYesAndNoes <$> asMap
boolsToYesAndNoes :: [Bool] -> YesAndNoes
boolsToYesAndNoes bools = do
let yes = length $ filter id bools
let no = length bools - yes
YesAndNoes yes no